WEB🔨/React

리액트를 다루는 기술 요약#2

최문경 블로그 2021. 6. 19. 19:19

해당 시리즈는 제목 그대로 김민준님의 '리액트를 다루는 기술'을 요약한 글입니다.

 

목차

3장 컴포넌트

4장 이벤트 핸들링

 

3장 컴포넌트

이 장에서는 먼저 클래스형 컴포넌트에 대해 살펴본 뒤, 컴포넌트를 새로 만들고 사용하는 방법을 알아보겠습니다. 그리고 컴포넌트의 속성 값을 지닌 props와 상태 값을 지닌 state를 사용하는 방법도 알아보겠습니다. - 84p

 

컴포넌트를 선언하는 방식

  1. 함수형
  2. 클래스형
// 함수형 컴포넌트
import React from 'react';

const App = () => {
  return (
    <div></div>
  );
}

export default App;
// 클래스형 컴포넌트
import React, { Component } from 'react'

class App extends Component {
  render() {
    return (
      <div></div>
    )
  }
}

export default App

 

리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수형 컴포넌트와 Hooks를 사용하도록 권장하고 있습니다. 하지만 클래스형 컴포넌트가 사라지는 것은 아니므로 클래스형 컴포넌트의 기능은 꼭 알아 두어야 합니다.

 

함수형 컴포넌트의 장점은 다음과 같습니다.

  1. 클래스형 컴포넌트보다 선언하기가 편하다.
  2. 클래스형 컴포넌트보다 메모리 자원을 덜 사용한다.
  3. 빌드한 후 배포할 때도 결과물의 파일 크기가 더 작다.

주요 단점은 state와 라이프사이클 API의 사용이 불가능하다는 점이었는데 리액트 v16.8 업데이트 이후 Hooks라는 기능이 도입되면서 해결되었습니다.

 

 

props

properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용

props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정

// App.js
import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
  return <MyComponent name="react" />;
};

export default App;
// MyComponent.js
import React from 'react';

const MyComponent = (props) => {
  return <div>제 이름은 {props.name}입니다.</div>;
};

export default MyComponent;

 

 

부모 컴포넌트에서 props를 설정하지 않았을 때 사용할 기본값은 defaultProps를 사용해 설정 가능

import React from 'react';

const MyComponent = (props) => {
  return <div>제 이름은 {props.name}입니다.</div>;
};

MyComponent.defaultProps = {
  name: '홍길동',
};

export default MyComponent;

 

 

컴포넌트의 필수 props를 지정하거나 props의 타입(type)을 지정할 때는 propTypes를 사용

import React from 'react';
import PropTypes from 'prop-types';

const MyComponent = (props) => {
  return (
    <div>
      <p>제 이름은 {props.name}입니다.</p>
      <p>제일 좋아하는 숫자는 {props.favoriteNumber}입니다.</p>
    </div>
  );
};

MyComponent.defaultProps = {
  name: '홍길동',
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired,
};

export default MyComponent;

 

 

children을 사용하면 태그 사이의 내용을 보여줄 수 있다.

import React from 'react';
import MyComponent from './MyComponent';

const App = () => {
  return <MyComponent>I Love React</MyComponent>;
};

export default App;
import React from 'react';

const MyComponent = (props) => {
  return (
    <div>
      <p>children은 {props.children}입니다.</p>
    </div>
  );
};

export default MyComponent;

 

 

비구조화 할당(구조 분해)를 사용하면 props 내부 값을 추출하여 더 편하게 사용할 수 있다.

import React from 'react';
import PropTypes from 'prop-types';

const MyComponent = (props) => {
  const { name, favoriteNumber, children } = props;
  return (
    <div>
      <p>제 이름은 {name}입니다.</p>
      <p>제일 좋아하는 숫자는 {favoriteNumber}입니다.</p>
      <p>children은 {children}입니다.</p>
    </div>
  );
};

MyComponent.defaultProps = {
  name: '홍길동',
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired,
};

export default MyComponent;
import React from 'react';
import PropTypes from 'prop-types';

const MyComponent = ({ name, favoriteNumber, children }) => {
  return (
    <div>
      <p>제 이름은 {name}입니다.</p>
      <p>제일 좋아하는 숫자는 {favoriteNumber}입니다.</p>
      <p>children은 {children}입니다.</p>
    </div>
  );
};

MyComponent.defaultProps = {
  name: '홍길동',
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired,
};

export default MyComponent;

 

 

클래스형 컴포넌트에서 props를 사용할 때는 render 함수에서 this.props를 조회

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class MyComponent extends Component {
  render() {
    const { name, favoriteNumber, children } = this.props;
    return (
      <div>
        <p>제 이름은 {name}입니다.</p>
        <p>제일 좋아하는 숫자는 {favoriteNumber}입니다.</p>
        <p>children은 {children}입니다.</p>
      </div>
    );
  }
}

MyComponent.defaultProps = {
  name: '홍길동',
};

MyComponent.propTypes = {
  name: PropTypes.string,
  favoriteNumber: PropTypes.number.isRequired,
};

export default MyComponent;

 

 

state

리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미

 

클래스형 컴포넌트의 state

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      fixedNumber: 0,
    };
  }

  render() {
    const { number, fixedNumber } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지 않는 값: {fixedNumber}</h2>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

 

 

state를 constructor에서 꺼내기

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0,
  };

  render() {
    const { number, fixedNumber } = this.state;
    return (...);
  }
}

export default Counter;

 

 

this.setState에 객체 대신 인자 전달하기

this.setState를 사용하여 state 값을 업데이트할 때는 비동기적으로 상대가 업데이트 됩니다. 따라서, 다음과 같이 onClick에 설정한 함수 내부에서 this.setState를 두 번 호출하면 +2가 되는 것이 아니라 +1만 됩니다.

onClick={() => {
    this.setState({ number: number + 1 });
    this.setState({ number: this.state.number + 1 });
}}

이에 대한 해결책은 다음과 같이 객체 대신에 함수를 인자로 넣어 주는 것입니다.

onClick={() => {
    this.setState(prevState => {
	return { number: prevState.number + 1 }
    });
    
    // { }를 생략하면 값을 바로 리턴해줍니다.
    this.setState(prevState => ({ number: prevState.number + 1 }));
}}

 

 

함수형 컴포넌트에서 useState 사용하기

  • useState 함수의 인자에는 상태의 초깃값을 넣는다.
  • 현재 상태와 상태를 바꾸어주는 함수를 리턴한다.
import React, { useState } from 'react';

const Counter = () => {
  const [number, setNumber] = useState(0);
  return (
    <div>
      <h1>{number}</h1>
      <button onClick={() => setNumber(number + 1)}>+1</button>
    </div>
  );
};

export default Counter;

 

 

4장 이벤트 핸들링

 

이벤트 사용시 주의사항

  • 이벤트 이름은 카멜 표기법으로 작성
  • 함수 형태의 값을 전달 (HTML에서 큰따옴표를 사용해 이벤트를 설정하는 것과 다름)
  • DOM 요소에만 이벤트 설정 가능 (컴포넌트에는 이벤트 설정 불가능)

 

import React, { Component } from 'react';

class EventPractice extends Component {
  state = {
    username: '',
    message: '',
  };

  onChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
    });
  };

  onSubmit = () => {
    alert(`${this.state.name}: ${this.state.message}`);
    this.setState({
      username: '',
      message: '',
    });
  };

  onKeyPress = (e) => {
    if (e.key === 'Enter') {
      this.onSubmit();
    }
  };

  render() {
    return (
      <div>
        <input
          type="text"
          name="username"
          placeholder="사용자명을 입력하세요."
          value={this.state.username}
          onChange={this.onChange}
        />
        <input
          type="text"
          name="message"
          placeholder="메시지를 입력하세요."
          value={this.state.message}
          onChange={this.onChange}
          onKeyPress={this.onKeyPress}
        />
        <button onClick={this.onSubmit}>확인</button>
      </div>
    );
  }
}

export default EventPractice;

 

 

 

함수형 컴포넌트로 구현해보기

import React, { useState } from 'react';

const EventPractice = () => {
  const [form, setForm] = useState({
    username: '',
    message: '',
  });

  const { username, message } = form;

  onChange = (e) => {
    const nextForm = {
      ...form,
      [e.target.name]: e.target.value,
    };

    setForm(nextForm);
  };

  onSubmit = () => {
    alert(`${username}: ${message}`);
    setForm({
      username: '',
      message: '',
    });
  };

  onKeyPress = (e) => {
    if (e.key === 'Enter') {
      onSubmit();
    }
  };

  return (
    <div>
      <input
        type="text"
        name="username"
        placeholder="사용자명을 입력하세요."
        value={username}
        onChange={onChange}
      />
      <input
        type="text"
        name="message"
        placeholder="메시지를 입력하세요."
        value={message}
        onChange={onChange}
        onKeyPress={onKeyPress}
      />
      <button onClick={onSubmit}>확인</button>
    </div>
  );
};

export default EventPractice;

 

우리가 8장에서 배울 useReducer와 커스텀 Hooks를 사용하면 이 작업을 훨씬 더 편하게 할 수도 있습니다.