리액트의 라이프 사이클에 대해 알아보기

고기호·2024년 10월 8일
1

React

목록 보기
1/1
post-thumbnail
post-custom-banner

간단히 알아보기

라이프 사이클의 세가지 단계

리액트의 라이프 사이클은 컴포넌트가 생성되고, 업데이트되고, 제거되는 과정에서 발생하는 여러 단계를 말한다. 주로 세 가지의 단계로 나눌 수 있다.

  1. Mounting
    • 컴포넌트가 처음 화면에 나타날 때 발생한다.
  2. Updating
    • 컴포넌트의 State 또는 Props가 변경될 떄 발생한다.
  3. Ummounting
    • 컴포넌트가 화면에서 사라질 때 발생한다.

함수형 컴포넌트에서 useEffect를 써본 경험이 많기 때문에 저 세단계가 존재한다고 예상을 하긴했다. 하지만 깊게 알지는 못했기 때문에 이번 기회에 블로깅하면서 정리를 해보려한다.

자세히 알아보기

0. 클래스형 컴포넌트와 함수형 컴포넌트


클래스형 컴포넌트

함수형 컴포넌트와는 달리 클래스형 컴포넌트는 라이프 사이클과 상태 관리를 기본적으로 처리할 수 있다고한다.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    // 컴포넌트가 처음 마운트될 때 실행되는 로직
  }

  componentDidUpdate() {
    // 컴포넌트가 업데이트될 때 실행되는 로직
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

위 코드에서 우리가 함수형 컴포넌트를 사용할 때 Hook을 Import해 라이프 사이클과 상태를 관리하는 것과 달리 클래스형 컴포넌트의 경우 componentDidMount(), componentDidUpdate(), this.state = { count: 0 } 같은 코드에서 Import문 없이 자체적으로 관리하는 것을 볼 수 있다.

함수형 컴포넌트

원래 React의 16.8버전 이전에는 useState와 useEffect같은 Hook들이 없었다. 따라서 당시에 함수형 컴포넌트는 상태가 없는 단순한 UI를 렌더링하는 정적인 컴포넌트였다. 그래서 자체적으로 상태와 라이프 사이클을 관리할 수 있는 클래스형 컴포넌트를 많이 사용했다. 하지만 Hook이 도입된 이후에는 두가지를 관리할 수 있고, 보일러 플레이트도 가벼운 함수형 컴포넌트를 많이 사용하기 시작했다.

import { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 컴포넌트가 마운트될 때 실행됨
    console.log('Component mounted');

    return () => {
      // 컴포넌트가 언마운트될 때 실행됨
      console.log('Component unmounted');
    };
  }, []);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

위 코드를 보면 useState와 useEffect 두가지 Hook을 Import해서 자체적이지 않은 방법으로 두가지를 관리를 하고 있다.

이무튼 이런 역사를 기반으로 생각해보면 기본적으로 함수형 컴포넌트는 두가지를 관리하지 못하지만 Hook을 통해 관리를 할 수 있다고 생각하면 될 듯 하다.

1. Mounting


새로운 컴포넌트가 생성되어 DOM에 삽입되는 단계이다. 단 한 번만 발생하며, 초기 렌더링이라고도 많이 말하는 듯 하다. 나는 예시를 참 좋아한다. 예시가 없으면 머릿속에서 글이 구조화 되기가 쉽지 않기 때문이다. 따라서 코드 예시를 먼저 들고 설명을 해보겠다.

import React from "react";

class ComponentDidMount extends React.Component {
    constructor(props) {
      super(props);
      console.log('Constructor called');
      this.state = {
        count: 0
      };
    }
  
    static getDerivedStateFromProps(props, state) {
      console.log('getDerivedStateFromProps called');
      return null;
    }
  
    componentDidMount() {
      console.log('componentDidMount called');
    }
  
    incrementCount = () => {
      this.setState(prevState => ({
        count: prevState.count + 1
      }));
    };
  
    render() {
      console.log('render called');
      return (
        <div>
          <h1>Counter App</h1>
          <p>Count: {this.state.count}</p>
          <button onClick={this.incrementCount}>Increment</button>
        </div>
      );
    }
}
  
export default ComponentDidMount;

constructor()

constructor 메서드는 해당 단계에서 가장 먼저 호출되는 메서드다. 컴포넌트의 상태를 초기화하고, props를 사용할 수 있게 해준다. 또한 컴포넌트 내에서 사용될 이벤트 핸들러 메서드를 바인딩하는 데 사용된다.

static getDerivedStateFromProps(props, state)

해석해보면 props로부터 파생된 상태를 가져온다는 걸 알 수 있듯이, props의 변화를 기반으로 상태를 업데이트할 수 있도록 해준다.

인자를 보면 Props, State 두 가지가 있는데 처음에 State가 왜 들어가지? 싶었다. 생각해보면 부모로부터 props를 내려받고 그 값으로 초기화되는 state가 있다면 업데이트 시 두 값이 같다면 불필요한 렌더링을 유발하니, 두 값을 비교하고 업데이트를 하기 때문인 듯 하다.

잘못 사용하면 많은 오류가 발생할 수 있어 초보자들은 피하는 것이 좋다고 한다.

render()

JSX로 HTML을 작성하고 DOM에 삽입하는 단계이다.

componentDidMount()

컴포넌트가 처음으로 렌더링된 후(render() 이후)에 호출된다. 함수형 컴포넌트에서 의존성 배열이 빈 useEffect를 생각하면 편할 듯 하다. 따라서 네트워크 요청 등의 사이드 이펙트를 관리하는데 적합하다.

2. Updating


컴포넌트가 업데이트되거나 다시 렌더링될 때 발생한다. props나 state가 변경될 때 트리거된다. 마찬가지로 먼저 예시 코드를 보자

import React from "react";

class UpdatingExample extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        name: 'John',
        changed: false
      };
      console.log('Constructor called');
    }
  
    static getDerivedStateFromProps(props, state) {
      console.log('getDerivedStateFromProps called');
      return null;
    }
  
    shouldComponentUpdate(nextProps, nextState) {
      console.log('shouldComponentUpdate called');
      return true;
    }

    getSnapshotBeforeUpdate(nextProps, nextState) {
        console.log('getSnapshotBeforeUpdate called');
        return null;
    }
  
    componentDidUpdate(prevProps, prevState) {
      console.log('componentDidUpdate called');
    }
  
    changeName = () => {
      this.setState({
        name: 'Jane',
        changed: true
      });
    };
  
    render() {
      console.log('render called');
      return (
        <div>
          <h1>Updating Example</h1>
          <div>Name {
              this.state.changed ? 
              <h3>{this.state.name}</h3> : 
              <p>{this.state.name}</p>}
          </div>
          <button onClick={this.changeName}>Change Name</button>
        </div>
      );
    }
}

export default UpdatingExample;

shouldComponentUpdate(nextProps, nextState)

불필요한 컴포넌트 리렌더링을 방지하는데 사용된다. 기본적으로 props와 state가 변경될 때 컴포넌트가 리렌더링되는데, true 또는 false를 반환해서 조건에 따라 컴포넌트를 리렌더링 할지 말지 결정할 수 있다.

nextProps와 nextState를 인자로 받아서 현재 props와 state와 비교할 수 있다.

getSnapshotBeforeUpdate(prevProps, prevState)

render() 직전에 호출된다. DOM이 업데이트되기 전의 상태를 기록할 수 있다. 주로 DOM의 스크롤 위치, 크기 변경 등의 정보를 기록할 때 유용하다. 반환된 값은 componentDidMount()의 세번 째 인자로 전달된다.

componentDidUpdate(prevProps, prevState, snapshot)

render() 이후에 호출된다. 주로 DOM 트리를 동작하거나, 전달된 인자중 하나가 변경될 때 발생하는 사이드 이펙트를 처리하는데 사용된다. useEffect에서 의존성 배열 내부의 데이터가 변경될 때 로직이 실행되는 것을 생각하면 편할 듯하다.

3. Ummounting


컴포넌트가 DOM에서 제거되고, 더 이상 렌더링되지 않거나 접근할 수 없을 때 발생한다. 이 단계에서 리액트를 컴포넌트와 관련된 리소스를 DOM 트리에서 제대로 정리하기 위해 몇가지 작업을 수행한다.

import React from "react";

class Child extends React.Component {
    componentDidMount() {
      console.log('Component mounted');
    }
  
    componentWillUnmount() {
      console.log('Component unmounted');
    }
  
    render() {
      return (
        <div>
          <p>Child Component content</p>
        </div>
      );
    }
}
  
export default class UnmountingExample extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        showComponent: true
      };
    }
    
    toggleComponent = () => {
      this.setState(prevState => ({
        showComponent: !prevState.showComponent
      }));
    };
  
    render() {
      return (
        <div>
          <h1>Main Component</h1>
          {this.state.showComponent && <Child />}
          <button onClick={this.toggleComponent}>
            {this.state.showComponent ? 'Unmount' : 'Mount'}
          </button>
        </div>
      );
    }
}

componentWillUnmount()

컴포넌트가 DOM에서 제거되기 직전에 호출된다. useEffect의 return문을 생각하면 될 듯하다. 그와 마찬가지로 타이머 취소, 이벤트 리스너 제거, 데이터 구조 정리 등 메모리 누수를 방지하기 위한 작업을 하는데 사용된다. 또한 컴포넌트의 모든 상태와 props는 소멸된다.

Reference

https://medium.com/@arpitparekh54/understanding-the-react-component-lifecycle-a-deep-dive-into-the-life-of-a-react-component-74813cb8dfb5
https://massivepixel.io/blog/react-lifecycle-methods/
https://levelup.gitconnected.com/react-lifecycle-methods-and-their-equivalents-in-functional-components-5677a3fa623d

profile
웹 개발자 고기호입니다.
post-custom-banner

0개의 댓글