React 개발의 세계에 깊이 발을 들여놓는다면, '컴포넌트'라는 개념을 빼놓고는 이야기할 수 없습니다. 컴포넌트는 모든 React 애플리케이션의 근간을 이루는 핵심 구성 요소이며, 사용자 인터페이스(UI)를 독립적이고 재사용 가능한 작은 단위로 분할하여 효율적으로 관리할 수 있도록 돕습니다. 오늘 이 글에서는 React 컴포넌트의 두 가지 주요 유형과 그들이 거치는 생명주기 단계에 대해 깊이 있게 탐구해보고자 합니다.
컴포넌트란 무엇이며 왜 중요한가?
React에서 컴포넌트는 UI의 특정 부분을 캡슐화하고 독립적으로 동작하게 만드는 블록입니다. 마치 레고 블록처럼, 개별 컴포넌트들을 조립하여 복잡한 UI를 구축할 수 있죠. 이는 코드의 재사용성을 높이고 유지보수를 용이하게 하며, 개발 과정을 훨씬 효율적으로 만듭니다.
React 컴포넌트의 두 가지 유형
React는 개발자가 필요에 따라 선택할 수 있는 두 가지 주요 컴포넌트 유형을 제공합니다.
- 함수 컴포넌트 (Function Components)
- JSX(JavaScript XML)를 반환하는 단순한 JavaScript 함수입니다.
- 주로 UI를 표시하는 '무상태(stateless)' 컴포넌트에 사용되었으나, React Hooks의 등장으로 이제는 상태 관리 및 생명주기 기능도 활용할 수 있게 되었습니다.
- 간결하고 직관적인 코딩이 가능하여 최근 React 개발의 주류를 이루고 있습니다.
function Greeting(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
- 클래스 컴포넌트 (Class Components)
React.Component를 상속받아 확장하는 ES6 클래스입니다.- 지역 상태 관리(
local state management) 및 생명주기 메서드(lifecycle methods)와 같은 더 많은 기능을 제공합니다. - 복잡한 로직이나 상태 변화가 필요한 경우에 유용하게 사용되었지만, 함수 컴포넌트와 Hooks의 발전으로 인해 사용이 점차 줄어들고 있는 추세입니다.
class Greeting extends React.Component {
render() {
return <h1>안녕하세요, {this.props.name}님!</h1>;
}
}
두 유형 모두 UI를 구성한다는 동일한 목적을 가지지만, 문법과 제공하는 기능 면에서 차이가 있습니다. 프로젝트의 복잡성, 팀의 선호도, 그리고 필요한 기능에 따라 적절한 컴포넌트 유형을 선택하는 것이 중요합니다.
React 컴포넌트 생명주기: 탄생부터 소멸까지
모든 React 컴포넌트는 애플리케이션 내에서 생성되고, 업데이트되며, 최종적으로 제거되는 일련의 단계를 거칩니다. 이를 '컴포넌트 생명주기(Component Lifecycle)'라고 부르며, 각 단계마다 특정 시점에 자동으로 호출되는 '생명주기 메서드'가 존재합니다. 개발자는 이 메서드들을 오버라이드하여 컴포넌트의 특정 시점에서 원하는 코드를 실행할 수 있습니다. 생명주기를 깊이 이해하는 것은 복잡한 상태 변화를 관리하고, 외부 API와 연동하며, 애플리케이션 성능을 최적화하는 데 필수적입니다.
1. 마운팅 (Mounting) 단계: 컴포넌트의 탄생
이 단계는 컴포넌트가 처음 생성되어 DOM(문서 객체 모델)에 삽입될 때 발생합니다. 즉, 화면에 컴포넌트가 처음 나타나는 시점입니다.
constructor():- 컴포넌트가 생성될 때 가장 먼저 호출되는 메서드입니다.
- 컴포넌트의 초기 상태(
this.state)를 설정하거나, 이벤트 핸들러를 바인딩하는 데 주로 사용됩니다. super(props)를 호출하여 부모 클래스의 생성자를 호출해야 합니다.
static getDerivedStateFromProps()(드물게 사용):- 렌더링 전에 props로부터 파생된 상태를 업데이트할 때 사용됩니다.
- 자주 사용되지는 않으며, 상태를 동기화하는 데 명확한 이유가 있을 때만 고려해야 합니다.
render():- 컴포넌트가 렌더링될 내용을 정의하는 유일한 필수 메서드입니다.
- JSX를 반환해야 하며, 이 메서드 내에서는 상태를 직접 변경해서는 안 됩니다. (순수 함수여야 합니다)
componentDidMount():- 컴포넌트가 DOM에 마운트된 직후 호출됩니다.
- 데이터 가져오기(fetching data), 구독(subscriptions) 설정, DOM 노드 직접 조작과 같은 초기화 작업에 이상적인 시점입니다.
2. 업데이트 (Updating) 단계: 변화에 반응하다
이 단계는 컴포넌트의 props 또는 state에 변경 사항이 발생할 때마다 발생합니다. React는 변경을 감지하고, 필요한 경우 UI를 다시 렌더링합니다.
static getDerivedStateFromProps()(다시 호출):- 새로운 props나 state에 따라 상태를 업데이트해야 할 때 호출됩니다.
shouldComponentUpdate(nextProps, nextState):props또는state변경에 따라 리렌더링이 필요한지 여부를 결정합니다.true를 반환하면 컴포넌트가 업데이트되고,false를 반환하면 업데이트가 건너뛰어집니다.- 불필요한 렌더링을 방지하여 성능을 최적화하는 데 기여할 수 있지만, 신중하게 사용해야 합니다.
render()(다시 호출):- 새로운
props와state를 기반으로 UI를 다시 렌더링합니다.
- 새로운
getSnapshotBeforeUpdate(prevProps, prevState)(드물게 사용):- 렌더링 결과가 DOM에 반영되기 직전에 호출됩니다.
- DOM에서 스크롤 위치와 같은 정보를 가져와
componentDidUpdate로 전달하는 데 유용합니다.
componentDidUpdate(prevProps, prevState, snapshot):- 업데이트가 발생한 직후 호출됩니다.
- 이전
props와state를 현재props와state와 비교하여 특정 조건에 따라 작업을 수행하는 데 유용합니다 (예: 네트워크 요청).
3. 언마운팅 (Unmounting) 단계: 깔끔한 마무리
이 단계는 컴포넌트가 DOM에서 제거될 때 발생합니다. 메모리 누수를 방지하고 리소스를 해제하는 중요한 시점입니다.
componentWillUnmount():- 컴포넌트가 DOM에서 제거되기 직전에 호출됩니다.
- 타이머 무효화(
clearInterval), 네트워크 요청 취소, 구독 해제와 같은 정리 작업(cleanup tasks)에 유용합니다. - 이 메서드에서 리소스를 해제하는 것이 메모리 누수를 방지하는 데 매우 중요합니다.
실용적인 예시: 카운터 애플리케이션으로 생명주기 이해하기
클래스 컴포넌트와 생명주기 메서드를 사용하여 간단한 카운터 애플리케이션을 만들어보며 위 개념들을 더 깊이 이해해봅시다.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this);
}
// 마운팅 단계: 컴포넌트가 DOM에 추가된 직후
componentDidMount() {
console.log('카운터가 마운트되었습니다');
// 예시: 1초마다 count를 증가시키는 인터벌 설정
this.interval = setInterval(() => {
console.log('인터벌이 실행 중입니다');
this.setState(prevState => ({ count: prevState.count + 1 }));
}, 1000);
}
// 업데이트 단계: props 또는 state 변경 시 리렌더링 여부 결정
shouldComponentUpdate(nextProps, nextState) {
// count가 5의 배수일 때만 업데이트
console.log('shouldComponentUpdate가 호출되었습니다. nextState.count:', nextState.count);
return nextState.count % 5 === 0 || nextState.count < 5;
}
// 업데이트 단계: 업데이트가 발생한 직후
componentDidUpdate(prevProps, prevState) {
console.log('카운터가 업데이트되었습니다. 이전 값:', prevState.count, '현재 값:', this.state.count);
}
// 언마운팅 단계: 컴포넌트가 DOM에서 제거되기 직전
componentWillUnmount() {
console.log('카운터가 언마운트됩니다. 인터벌을 정리합니다.');
clearInterval(this.interval);
}
increment() {
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
console.log('render()가 호출되었습니다.');
return (
<div>
<h2>카운터: {this.state.count}</h2>
<button onClick={this.increment}>증가</button>
</div>
);
}
}
이 코드를 실행하고 콘솔을 확인해보면, 컴포넌트가 생성, 업데이트, 제거되는 과정에서 각 생명주기 메서드가 언제 호출되는지 명확하게 이해할 수 있습니다. 특히 shouldComponentUpdate를 통해 불필요한 렌더링을 막아 성능을 최적화하는 방법을 볼 수 있습니다.
결론: 왜 생명주기 이해가 중요한가?
React 개발에서 컴포넌트 생명주기를 이해하는 것은 단순히 지식을 쌓는 것을 넘어섭니다. 이는 곧 애플리케이션의 동작 방식과 성능을 완벽하게 제어할 수 있는 능력을 의미합니다. 올바른 시점에 데이터를 가져오고, 불필요한 리소스를 해제하며, 사용자 경험을 최적화하는 것 모두 생명주기에 대한 깊은 이해에서 비롯됩니다.
함수 컴포넌트와 Hooks가 대세가 되면서 클래스 컴포넌트의 생명주기 메서드들이 useEffect와 같은 Hook으로 대체되고 있지만, 그 근본적인 개념과 원리는 여전히 동일합니다. 컴포넌트의 탄생과 소멸을 이해하는 것은 어떤 기술 스택을 사용하든 견고한 React 애플리케이션을 구축하는 데 필수적인 기초가 됩니다.
'프로그래밍 > ReactJS' 카테고리의 다른 글
| React State: 동적인 UI를 위한 핵심 개념 마스터하기 (0) | 2025.09.19 |
|---|---|
| ReactJS, 사용자 경험의 심장을 뛰게 하는 이벤트 처리 완벽 가이드 💻 (0) | 2025.09.19 |
| React 컴포넌트와 상태(State) 완전 정복: 동적인 UI 개발의 핵심! (0) | 2025.09.19 |
| React 컴포넌트와 프롭스: 인터랙티브 UI 구축의 핵심! (0) | 2025.09.19 |
| React 클래스 컴포넌트 마스터하기: 여전히 중요한 이유와 핵심 기능 (0) | 2025.09.19 |