리액트 컴포넌트의 핵심: 구성과 데이터 관리¶
리액트 애플리케이션을 구성하는 가장 작은 단위인 컴포넌트는 크게 클래스형 컴포넌트와 함수형 컴포넌트로 나뉩니다. 이 글에서는 두 컴포넌트의 특징과 함께, 데이터를 다루는 핵심 개념인 props와 state에 대해 알아보겠습니다.
1. 클래스형 컴포넌트 vs 함수형 컴포넌트¶
클래스형 컴포넌트¶
클래스형 컴포넌트는 다음과 같은 특징을 가집니다.
- state 기능 및 라이프사이클(Lifecycle) 기능을 사용할 수 있습니다.
- 임의의 메서드를 정의하여 로직을 분리할 수 있습니다.
- 필수 사항: render() 함수가 반드시 존재해야 하며, 그 안에서 보여주어야 할 JSX를 반환해야 합니다.
함수형 컴포넌트¶
최근 리액트 개발의 주류가 된 함수형 컴포넌트의 특징입니다.
- 장점:
- 클래스형 컴포넌트보다 선언하기가 훨씬 편합니다.
- 메모리 자원을 덜 사용하며, 빌드 후 배포할 때 파일 크기가 더 작습니다. (다만, 성능과 크기 면에서 클래스형과 극적인 차이는 없습니다.)
- 단점:
- 초기에는 state와 라이프사이클 API 사용이 불가능했습니다.
- 해결: 리액트 v16.8 이후 Hooks(useState, useEffect 등) 기능이 도입되면서 클래스형 컴포넌트의 기능을 대부분 대체할 수 있게 되었습니다.
2. 컴포넌트의 속성: props¶
props는 'properties'의 줄임말로, 컴포넌트가 부모 컴포넌트로부터 전달받는 속성입니다.
- 설정: 부모 컴포넌트에서 자식 컴포넌트를 불러와 사용할 때 설정합니다.
- 사용:
- 함수형 컴포넌트: 파라미터로 받아와 사용.
- 클래스형 컴포넌트:
this.props를 통해 접근.
- 기능:
defaultProps: props의 기본값을 설정할 수 있습니다.children: 태그 사이의 내용을 보여줄 때 사용합니다.
- 비구조화 할당:
const { name, children } = props;와 같이 내부 값을 직관적으로 추출하여 사용할 수 있습니다.
propTypes를 통한 검증¶
컴포넌트의 props 타입을 지정하여 예기치 못한 에러를 방지할 수 있습니다.
import PropTypes from 'prop-types';
MyComponent.propTypes = {
name: PropTypes.string,
age: PropTypes.number.isRequired // 필수 설정
};
다양한 PropTypes 종류:
- array, bool, func, number, object, string, symbol
- node: 렌더링할 수 있는 모든 것(숫자, 문자열, JSX, children)
- instanceOf(클래스): 특정 클래스의 인스턴스
- oneOf(['dog', 'cat']): 배열 요소 중 하나
- oneOfType([PropTypes.string, PropTypes.number]): 주어진 종류 중 하나
- shape({name: PropTypes.string, num: PropTypes.number}): 특정 스키마를 가진 객체
3. 컴포넌트 내부의 상태: state¶
state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미합니다. 부모가 결정하는 props와 달리, 컴포넌트 스스로가 관리합니다.
useState (함수형 컴포넌트)¶
클래스형 컴포넌트의 state가 반드시 객체여야 했던 것과 달리, useState는 어떤 형태의 값(숫자, 문자열, 객체 등)이든 가질 수 있습니다.
- 사용법:
const [value, setValue] = useState(초기값); - 반환값: 첫 번째 원소는 현재 상태, 두 번째 원소는 상태를 바꾸어 주는 함수(setter)입니다.
- 주의사항: 상태를 업데이트할 때는 반드시 세터 함수를 사용해야 합니다. 배열이나 객체를 업데이트할 때는 원본을 직접 수정하지 않고, 사본을 만들어 업데이트한 후 세터 함수로 전달해야 합니다.
4. 정리: props vs state¶
- props: 부모 컴포넌트가 설정하며, 자식 컴포넌트 입장에서는 읽기 전용입니다.
- state: 컴포넌트가 자체적으로 지닌 값으로, 내부에서 업데이트할 수 있습니다.
비록 용도는 다르지만 둘 다 렌더링 결과에 영향을 주는 데이터를 담고 있습니다. 효율적인 아키텍처를 위해 부모의 state를 자식의 props로 전달하거나, 자식의 이벤트가 발생할 때 부모의 메서드를 호출하여 상위 state를 변경하는 방식으로 유동적인 데이터 흐름을 구현할 수 있습니다.