프로그래밍/ReactJS

리액트(React) 컴포넌트 생명주기 완벽 이해: 동적인 애플리케이션 구축의 핵심 🚀

shimdh 2025. 9. 20. 10:04
728x90

리액트 개발자라면 컴포넌트의 상태(state)생명주기(lifecycle)를 깊이 이해하는 것이 필수입니다. 이는 단순히 코드를 작성하는 것을 넘어, 효율적이고 반응성 높은 동적인 애플리케이션을 구축하기 위한 초석이 됩니다. 이번 포스팅에서는 리액트 컴포넌트의 생명주기를 세 가지 주요 단계로 나누어 각 단계에서 어떤 메서드들이 작동하며, 이를 어떻게 활용해야 하는지 자세히 알아보겠습니다.


리액트 컴포넌트 생명주기의 3가지 주요 단계

리액트 컴포넌트의 생명주기는 크게 마운팅(Mounting), 업데이트(Updating), 그리고 언마운팅(Unmounting)의 세 가지 단계로 나눌 수 있습니다. 각 단계는 컴포넌트의 존재 주기에서 특정 시점을 나타내며, 개발자가 이 시점에 개입하여 원하는 작업을 수행할 수 있도록 특별한 메서드들을 제공합니다.


728x90

1. 마운팅 단계 (Mounting Phase) 🏗️

마운팅 단계는 컴포넌트가 처음 생성되어 DOM(문서 객체 모델)에 삽입되는 과정을 의미합니다. 이 단계는 컴포넌트가 화면에 나타나기 위해 준비하는 과정이라고 할 수 있습니다.

  • constructor()
    • 호출 시점: 마운팅 프로세스에서 가장 먼저 호출됩니다.
    • 주요 용도:
      • 컴포넌트의 초기 상태(this.state)를 설정합니다. 예를 들어, this.state = { count: 0 };와 같이 초기 값을 지정할 수 있습니다.
      • 이벤트 핸들러를 바인딩합니다. this.handleClick = this.handleClick.bind(this);와 같이 'this' 컨텍스트를 명시적으로 바인딩하여 함수 내부에서 컴포넌트 인스턴스에 접근할 수 있도록 합니다.

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 }; // 초기 상태 설정
        this.handleClick = this.handleClick.bind(this); // 이벤트 핸들러 바인딩
    }
}
  • componentDidMount()
    • 호출 시점: 컴포넌트가 DOM에 마운트(삽입)된 직후에 호출됩니다. 즉, 컴포넌트가 화면에 성공적으로 나타난 후입니다.
    • 주요 용도:
      • API 호출: 외부 데이터를 가져오는 비동기 작업을 시작하기에 가장 적합한 위치입니다. 이 메서드에서 데이터를 가져와 컴포넌트의 상태를 업데이트하면 화면에 데이터가 로드됩니다.
      • 구독 설정: 웹소켓 연결이나 타이머와 같은 구독을 설정할 때 사용됩니다.
      • DOM 직접 조작: React가 제공하는 추상화 계층을 넘어서 특정 DOM 노드를 직접 조작해야 할 때 유용합니다. (가급적 지양하는 것이 좋습니다.)

componentDidMount() {
    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => this.setState({ data })); // 데이터 로드 후 상태 업데이트
}

2. 업데이트 단계 (Updating Phase) 🔄

업데이트 단계는 컴포넌트의 props 또는 state가 변경될 때 발생하며, 이로 인해 컴포넌트가 다시 렌더링될 때까지 이어집니다. 이 단계는 컴포넌트의 동적인 특성을 담당합니다.

  • shouldComponentUpdate(nextProps, nextState)
    • 호출 시점: 새로운 propsstate가 수신되었을 때 render() 메서드가 호출되기 전에 호출됩니다.
    • 주요 용도:
      • 성능 최적화: 기본적으로 propsstate가 변경되면 컴포넌트는 항상 재렌더링됩니다. 하지만 이 메서드에서 false를 반환하면 불필요한 재렌더링을 방지하고 애플리케이션의 성능을 최적화할 수 있습니다.
      • 현재 propsnextProps, 그리고 현재 statenextState를 비교하여 실제 변경이 있을 때만 true를 반환하도록 로직을 작성합니다.

shouldComponentUpdate(nextProps, nextState) {
    // nextProps와 현재 props를 비교하여 업데이트 여부 결정
    return nextProps.value !== this.props.value || nextState.count !== this.state.count;
}
  • render()
    • 호출 시점: propsstate가 변경되거나, forceUpdate()가 호출되었을 때 호출됩니다.
    • 주요 용도:
      • 현재 propsstate를 기반으로 화면에 표시되어야 할 JSX(JavaScript XML) 표현을 반환합니다.
      • 순수 함수: render() 메서드는 순수해야 합니다. 즉, 컴포넌트의 상태를 변경하거나 API 호출과 같은 사이드 이펙트를 발생시키지 않아야 합니다. 단순히 데이터를 받아 화면에 그리는 역할만 수행해야 합니다.
  • componentDidUpdate(prevProps, prevState)
    • 호출 시점: 업데이트가 발생한 후(즉, render() 메서드 호출 및 DOM 업데이트 후에) 호출됩니다.
    • 주요 용도:
      • props 또는 state 변경에 기반한 작업을 수행할 수 있습니다. 예를 들어, 특정 props가 변경되었을 때 새로운 데이터를 다시 가져오는 등의 비동기 작업을 처리하기에 적합합니다.
      • 업데이트된 DOM을 직접 조작해야 할 때 사용됩니다.

componentDidUpdate(prevProps, prevState) {
    // userId가 변경되었을 때 사용자 데이터 다시 가져오기
    if (this.props.userId !== prevProps.userId) {
        this.fetchUserData(this.props.userId);
    }
}

3. 언마운팅 단계 (Unmounting Phase) 🧹

언마운팅 단계는 컴포넌트가 DOM에서 제거될 때 발생합니다. 이 단계는 메모리 누수를 방지하고 애플리케이션의 안정성을 유지하는 데 매우 중요합니다.

  • componentWillUnmount()
    • 호출 시점: 컴포넌트가 DOM에서 제거되기 직전에 호출됩니다.
    • 주요 용도:
      • componentDidMount에서 설정된 모든 리소스(타이머, 구독, 네트워크 요청 등)를 정리(clean up)해야 합니다. clearInterval(), clearTimeout(), 구독 해제 등의 작업을 수행합니다.
      • 이 메서드에서 클린업 작업을 수행하지 않으면 메모리 누수가 발생하거나 예기치 않은 동작이 발생할 수 있습니다.

componentWillUnmount() {
    clearInterval(this.interval); // 타이머 정리
    // 다른 클린업 작업들...
}

실제 예시: 타이머 컴포넌트 구현 ⏱️

이러한 생명주기 메서드들을 활용하여 간단한 타이머 컴포넌트를 구현하는 예시를 살펴보겠습니다.

class Timer extends React.Component {
    constructor(props) {
        super(props);
        this.state = { secondsElapsed: 0 }; // 초기 상태 설정

        // handleTick 함수에 'this' 컨텍스트 바인딩
        this.handleTick = this.handleTick.bind(this);

        // interval 변수 설정
        this.interval = null; // 초기에는 null로 설정
    }

    // 컴포넌트가 마운트될 때 타이머 시작
    componentDidMount() {
        // 매 초마다 handleTick 함수 호출
        this.interval = setInterval(this.handleTick, 1000);
    }

    // 매 틱마다 경과 시간 증가
    handleTick() {
        this.setState((prevState) => ({
            secondsElapsed: prevState.secondsElapsed + 1,
        }));
    }

    // 컴포넌트가 언마운트될 때 interval 정리
    componentWillUnmount() {
        clearInterval(this.interval); // 타이머 중지
    }

    render() {
        return (
            <div>
                <h1>경과 시간:</h1>
                <p>{this.state.secondsElapsed} 초</p>
            </div>
        );
    }
}
728x90