React 컴포넌트는 브라우저 화면에 나타나고(Mount), 상태가 바뀌어 업데이트되고(Update), 화면에서 사라지는(Unmount) 일련의 생애주기를 거친다.
과거 클래스형 컴포넌트에서 사용하던 생명주기 메서드들과 현재 표준인 함수형 컴포넌트의 useEffect 훅을 1:1로 매핑하여 뭐가 다른지 봐봅시다.
클래스형 컴포넌트는 생애주기의 각 단계마다 브라우저가 특정 메서드를 자동으로 호출하도록 설계되어 있다.
render(): 컴포넌트의 JSX를 가상 DOM에 그린다.componentDidMount(): 컴포넌트가 실제 DOM에 반영된 직후 1회 실행된다.componentDidUpdate(prevProps, prevState): 리렌더링이 끝나고 DOM 업데이트가 완료된 후 실행된다.componentWillUnmount(): 컴포넌트가 DOM에서 완전히 제거되기 직전에 실행된다.import React, { Component } from 'react';
export default class CounterPage extends Component {
componentDidMount() {
console.log("마운트 완료");
}
componentDidUpdate() {
console.log("업데이트 완료");
}
componentWillUnmount() {
console.log("언마운트 직전");
}
render() {
return <div>카운터 페이지</div>;
}
}
함수형 컴포넌트는 별도의 메서드 대신 useEffect 훅과 의존성 배열(deps)의 조합으로 이 생명주기를 대체한다.
componentDidMount)의존성 배열에 빈 배열([])을 넘기면 최초 렌더링 이후 단 한 번만 실행된다.
useEffect(() => {
console.log("마운트 완료");
}, []);
componentDidUpdate)배열에 감시할 상태값을 지정하면 해당 값이 바뀔 때마다 실행된다.
useEffect(() => {
console.log("someState 변경됨:", someState);
}, [someState]);
주의: componentDidUpdate와의 결정적 차이
componentDidUpdate는 최초 마운트 시점에는 실행되지 않고 오직 '업데이트(리렌더링)' 시점에만 호출된다.useEffect(fn, [someState])는 의존성 배열에 값이 있더라도 최초 마운트 시점에 기본적으로 1회 실행된 후, 값이 바뀔 때마다 추가로 실행된다.componentWillUnmount)useEffect 콜백 안에서 정리(Clean-up) 함수를 return하면 컴포넌트가 화면에서 사라질 때 실행된다.
useEffect(() => {
console.log("마운트 완료");
// 언마운트 시 실행되는 뒷정리 함수
return () => {
console.log("언마운트 완료 (이벤트 리스너 해제, 타이머 정리)");
};
}, []);
| 생명주기 단계 | 클래스형 컴포넌트 | 함수형 컴포넌트 (useEffect) |
|---|---|---|
| 마운트 (화면 등장) | componentDidMount() | useEffect(() => {}, []) |
| 업데이트 (리렌더링) | componentDidUpdate() | useEffect(() => {}, [state]) (단, 마운트 시에도 1회 실행) |
| 매 렌더링마다 | render() 직후 별도 분기 | useEffect(() => {}) (deps 생략) |
| 언마운트 (화면 퇴장) | componentWillUnmount() | useEffect(() => { return () => {} }, []) |
클래스형 컴포넌트는 생명주기 단계별로 코드가 분산되어 동일한 기능(예: 이벤트 등록과 해제)이 서로 다른 메서드로 쪼개지는 한계가 있었다.
반면 함수형 컴포넌트의 useEffect는 관련 있는 로직(구독과 구독 해제)을 하나의 블록 안에서 묶어 처리할 수 있어 코드 응집도와 가독성이 훨씬 높다.