리액트 Summary - 2-1. 클래스형 라이프사이클 총정리-thumbnail

리액트 Summary - 2-1. 클래스형 라이프사이클 총정리

Class Component Lifecycle summary
380

Lifecycle - Class Component

처음 rendering할 때 해주어야 할 작업이 있거나, 업데이트와 관련되어 어떤 동작을 해주어야 할 때, 어떤 component를 없앨 때 무언가를 해주고 싶거나 등, 다양한 상황(lifecycle)에서 병행되어야 하는 내용을 구현하고 싶을 때 활용하게 되는 메서드들이 있는데, 이들이 React의 Lifecycle 메서드들이다. 총 9가지 경우가 있고, 이들이 호출되는 경우에는 총 세 가지 경우가 있다. mount, update, unmount가 그 셋인데, 각각의 상황에서 쓰이는 메서드들을 알아보자. 그리고 추가적으로 React 16부터 도입된 Error 관련 메서드도 한 가지 살펴보게 될 것이다.

(src/index.js)

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>
    <App />
  // </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

참고로 <React.StrictMode> 태그를 사용하게 되면 디버깅을 자동으로 하는 바람에 메서드들이 두 번씩 호출될 수 있다. 좀 더 lifecycle 메서드가 production 환경에서 어떻게 돌아갈지 정확히 느끼고 싶다면 위 코드처럼 해당 태그를 잠시 주석처리하고 진행할 것을 권장한다.

Mount

DOM이 생성되고 브라우저에 해당 화면이 나타나는 작업을 mount라고 한다. 호출되는 메서드의 순서는 아래와 같다.

constructor -> getDerivedStateFromProps -> render -> componentDidMount

(src/App.js)

import { Component } from 'react';
import LifecycleMount from './LifecycleMount';

function getRandomColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

class App extends Component {
  state = {
    color: '#000000'
  }

  handleClick = () => {
    this.setState({
      color: getRandomColor()
    });
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Random Color Generator</button>
        <LifecycleMount color={this.state.color} idNumber={1}>LifecycleMount</LifecycleMount>
      </div>
    );
  }
}

export default App;

(src/LifecycleMount.js)

import { Component } from 'react';

class LifecycleMount extends Component {
  state = {
    color: null
  }

  constructor(props) {
    super(props);
    console.log('called: constructor');
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('called: getDerivedStateFromProps');

    if(nextProps.color !== prevState.color){
      return { color: nextProps.color };
    }

    return null;
  }

  render() {
    console.log('called: render');

    const style = {
      color: this.props.color
    };

    return (
      <div>
        <p style={style}>color: {this.state.color}</p>
      </div>
    );
  }

  componentDidMount() {
    console.log('called: componentDidMount');
  }
}

export default LifecycleMount;

1. constructor

  constructor(props) {
    super(props);
    console.log('called: constructor');
  }

Component를 새로 만들 때마다 호출된다. 사실 메서드를 binding하거나 state를 초기화하는 용도로 사용되는 경우가 많다. 그 두 작업 모두 불필요한 상황이라면 생략해도 좋다. 특히 state를 직접 할당할 수 있는 유일한 메서드라는 점을 명심하면 좋다.

2. getDerivedStateFromProps

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('called: getDerivedStateFromProps');

    if(nextProps.color !== prevState.color){
      return { color: nextProps.color };
    }

    return null;
  }

이 메서드는 render 메서드가 호출되기 직전에 호출된다(그러므로 매 rendering 때마다 실행된다는 점에 유의하자). props에 있는 값이 변하면서 이것이 state에 영향을 줄 때 무언가를 하기 위해 구현된 메서드다.

React 공식문서에서는 부수효과 발생이 필요한 경우에는 componentDidUpdate를, 일부 data를 다시 계산해야 할 때에는 Memorization Helper를, state 재설정이 필요한 경우에는 완벽하게 제어되고 있는 component를 활용할 것을 권하기는 한다.

앞에 static이 붙는 것에 유의하자.

3. render

  render() {
    console.log('called: render');

    const style = {
      color: this.props.color
    };

    return (
      <div>
        <p style={style}>color: {this.state.color}</p>
      </div>
    );
  }

아주 익숙한 녀석이다. 얘도 lifecycle 메서드 중 하나다. Component의 UI를 render할 때 호출된다. Class Component에서 반드시 구현되어야 하는 유일한 메서드로, 무조건 React element, array, fragment, protal, 문자열이나 숫자, Boolean이나 null 중 하나를 반환해야 한다.

또한 React 공식 홈페이지에서는 render() 내에서 브라우저와 상호작용하지 말고 componentDidMount와 같은 다른 lifecycle 메서드에서 상호작용할 것을 권장하고 있다.

4. componentDidMount

component가 tree에 삽입된 직후에(마운트된 직후에) 호출된다. React 공식문서에서는 subscription 관련 작업이 이루어지면 좋다고 말한다.

Update

props가 바뀌거나, state가 바뀌거나, parent component가 re-rendering되거나, this.forceUpdate가 호출될 때 update가 일어난다. 이때 호출되는 메서드의 순서는 아래와 같다.

getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate

(src/LifecycleUpdate.js)

import { Component } from 'react';

class LifecycleUpdate extends Component {
  state = {
    number: 0, 
    color: null
  }

  handleClick = () => {
    this.setState({
      number: this.state.number + 1
    });
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('called: getDerivedStateFromProps');

    if(nextProps.color !== prevState.color){
      return { color: nextProps.color };
    }

    return null;
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('called: shouldComponentUpdate');

    return nextState.number % 10 !== 4;
  }

  render() {
    console.log('called: render');

    const style = {
      color: this.props.color
    };

    return (
      <div>
        <p>number: {this.state.number}</p>
        <p style={style} ref={ref => this.myRef=ref}>color: {this.state.color}</p>
        <button onClick={this.handleClick}>
          plus
        </button>
      </div>
    );
  }

  
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('called: getSnapshotBeforeUpdate');

    if(prevProps.color !== this.props.color){
      return this.myRef.style.color;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('called: componentDidUpdate');
    if(snapshot){
      console.log('snapshot: ', snapshot);
    }
  }
}

export default LifecycleUpdate;

1. getDerivedStateFromProps

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('called: getDerivedStateFromProps');

    if(nextProps.color !== prevState.color){
      return { color: nextProps.color };
    }

    return null;
  }

이 메서드는 render 메서드가 호출되기 직전에 호출된다(그러므로 매 rendering 때마다 실행된다는 점에 유의하자). props에 있는 값이 변하면서 이것이 state에 영향을 줄 때 무언가를 하기 위해 구현된 메서드다.

React 공식문서에서는 부수효과 발생이 필요한 경우에는 componentDidUpdate를, 일부 data를 다시 계산해야 할 때에는 Memorization Helper를, state 재설정이 필요한 경우에는 완벽하게 제어되고 있는 component를 활용할 것을 권하기는 한다.

앞에 static이 붙는 것에 유의하자.

2. shouldComponentUpdate

  shouldComponentUpdate(nextProps, nextState) {
    console.log('called: shouldComponentUpdate');

    return nextState.number % 10 !== 4;
  }

props나 state에 변화가 생겨 rendering을 하기 직전에 호출된다. 성능 최적화가 목적이기에 이 메서드에서 rendering 방지를 시도할 경우 에러의 원인이 될 수 있다. 그런 작업이 필요한 경우에는 React의 PureComponent를 사용하는 것이 좋다고 한다. 물론 예시에서처럼 nextProps, nextStatethis.state, this.props 등과 비교하여 비슷한 작업을 할 수는 있다.

true나 false를 꼭 return하여 update 여부를 알려주어야 한다는 것이 특징이다.

3. render

  render() {
    console.log('called: render');

    const style = {
      color: this.props.color
    };

    return (
      <div>
        <p>number: {this.state.number}</p>
        <p style={style} ref={ref => this.myRef=ref}>color: {this.state.color}</p>
        <button onClick={this.handleClick}>
          plus
        </button>
      </div>
    );
  }

Component의 UI를 render할 때 호출된다. Class Component에서 반드시 구현되어야 하는 유일한 메서드로, 무조건 React element, array, fragment, protal, 문자열이나 숫자, Boolean이나 null 중 하나를 반환해야 한다.

또한 React 공식 홈페이지에서는 render() 내에서 브라우저와 상호작용하지 말고 componentDidMount와 같은 다른 lifecycle 메서드에서 상호작용할 것을 권장하고 있다.

4. getSnapshotBeforeUpdate

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('called: getSnapshotBeforeUpdate');

    if(prevProps.color !== this.props.color){
      return this.myRef.style.color;
    }
    return null;
  }

마지막 rendering 결과물이 DOM 등에 반영되기 직전에 호출된다. DOM의 스크롤 위치 등과 같은 정보를 변경 이전에 얻어올 수 있다. 드물게 쓰이며 주로 스크롤 위치와 같은 정보가 필요한 UI에서 사용한다. prevProps, prevState 등을 인자로 받아 사용한다.

이 함수에서 return된 값은 componentDidUpdate에서 확인할 수 있다.

5. componentDidUpdate

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('called: componentDidUpdate');
    if(snapshot){
      console.log('snapshot: ', snapshot);
    }
  }

Rendering 갱신이 일어난 직후에 호출된다. props를 비교하거나 component 갱신에 따른 추가 DOM 조작 등이 필요한 경우 사용한다. this.propsprevProps를 비교하는 등의 조건문을 통해 추가 작업을 정의하는 것이 component 성능상으로도 좋다고 한다. 또한 props를 state에 그대로 저장하는 것은 에러의 원인이 되기도 하여 권장되지 않는 사항이라고 한다.

snapshot parameter를 통해 getSnapshotBeforeUpdate에서 return한 값을 찾아볼 수도 있다.

Unmount

어떤 component가 DOM에서 제거되는 것을 unmount라고 한다. componentWillUnmount 메서드만이 호출된다.

componentWillUnmount

component가 unmount될 때 호출된다. Event, timer, DOM 등 직접 생성한 것들 중 제거해주어야 하는 것들이 있을 때 이 메서드 내에서 작업을 해주면 된다.

Catch error

componentDidCatch

(src/App.js)

import ErrorBoundary from './ErrorBoundary'

(...)

<ErrorBoundary>
  <LifecycleError color={this.state.color} />
</ErrorBoundary>

(...)

이전 App.js 코드에서 에러를 발생시킬 component를 ErrorBoundary라는 component로 감싸자. ErrorBoundary.js는 아래와 같이 작성하자.

(src/ErrorBoundary.js)

import { Component } from 'react';

class ErrorBoundary extends Component {
  state = {
    error: false
  }

  componentDidCatch(error, info) {
    this.setState({
      error: true
    });

    console.log('called: comopnentDidCatch', {error, info});
  }

  render() {
    console.log('called: ErrorBoundary render')
    if(this.state.error) return <div>Error occurred!</div>;
    return this.props.children;
  }
}

export default ErrorBoundary;

componentDidCatch는 두 parameter를 전달받는다. error는 발생한 에러에 대한 정보를 담고 있고, info는 어떤 component에서 에러가 발생했는지에 대한 정보를 담고 있는 객체다. 여기서는 state에 error를 담아둔 뒤, this.setState를 활용해 error의 상태를 변경해주고 이 상태를 받아서 render 시에 error가 발생했으면 자식 component를 render하는 것이 아닌 에러를 확인할 수 있는 다른 component를 return하고 있다.

이제 본 component에서 error를 발생시켜보자.

(src/LifeCycleError.js)

import { Component } from 'react';

class LifeCycleError extends Component {
  render() {
    console.log('called: LifeCycleError render');

    return (
      <div>
        {this.props.missing.value}
      </div>
    );
  }
}

export default LifeCycleError;

참고 서적 :
김민준, 2019, 길벗, 『리액트를 다루는 기술, 개정판』

참고 사이트 :
React.Component