본문 바로가기
개발/react

react 클래스 컴포넌트 생명주기(LifeCycle) 메소드 이해

by 궁즉변 변즉통 통즉구 2023. 9. 5.
반응형

모든 react 컴포넌트는 페이지 렌더링 되기전 준비 과정부터 페이지에서 사라질 때까지 생명주기(라이프사이클)가 존재한다. 컴포넌트를 처음으로 렌더링할 때 어떤 작업을 처리하거나 컴포넌트 업데이트 전후로 어떤 작업을 처리해야 할 때 등에 컴포넌트 생명주기 메소드를 사용할 수 있다. 참고로 본문의 생명주기 메소드는 클래스 컴포넌트에서만 사용 가능하고 함수형 컴포넌트에서는 사용할 수 없다. 함수형 컴포넌트에서는 Hooks기능을 사용하여 비슷한 작업을 처리 할 수 있는데 이는 별도로 알아볼 예정이다.

 

출처: https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

 

 

react 라이프사이클은 마운트, 업데이트, 언마운트 이렇게 3가지로 나뉜다. 

1. 마운트(생성)

DOM이 생성되고 웹 브라우저에 나타나는 것을 마운트라고 한다. 이 때의 호출되는 메소드는 아래와 같다.

constructor -> getDerivedStateFromProps -> render -> componentDidMount

 

메소드 설명
constructor 컴포넌트를 새로 만들때 마다 호출되는 클래스 생성자
초기 state 정의 가능
getDerivedStateFromProps props에 있는 값을 state에 넣을 때 사용하는 메소드
(props의 변화에 따라 state도 변경 할 경우 사용)
render UI 렌더링 메소드
componentDidMount 컴포넌트가 브라우저에 나타난 이후에 호출되는 메소드
다른 js라이브러리 호출 및 네트워크 요청, 이벤트 등록 등 처리

 

2. 업데이트

컴포넌트가 업데이트 되는 상황은 아래와 같다.

  1. props 변경 시

  2. state 변경 시 

  3. 부모 컴포넌트가 리렌더링 될 경우

  4. this.forceUpdate로 강제로 렌더링 할 경우

 

업데이트 시 호출하는 메소드는 다음과 같다.

getDerivedStateFromProps -> shouldComponentUpdate -> (true 반환 시)
-> render -> getSnapshotBeforeUpdate -> componentDidMount

 

메소드 설명
getDerivedStateFromProps props에 있는 값을 state에 넣을 때 사용하는 메소드
(props의 변화에 따라 state도 변경 할 경우 사용)
shouldComponentUpdate 컴포넌트가 리렌더링 할지 말지 결정하는 메소드(true/false 반환)
false 반환 시 작업 중지
render UI 렌더링 메소드
getSnapshotBeforeUpdate 컴포넌트 변화를 DOM에 반영하기 직전에 호출되는 메소드
업데이트하기 직전의 값을 참고할 때 활용
componentDidMount 컴포넌트가 브라우저에 나타난 이후에 호출되는 메소드
다른 js라이브러리 호출 및 네트워크 요청, 이벤트 등록 등 처리

 

3. 언마운트(제거)

컴포넌트를 DOM에서 제거하는 것을 언마운트라고 한다. 이 때의 호출되는 메소드는 아래와 같이 1개 뿐이다.

componentWillUnmount

 

메소드 설명
componentWillUnmount 컴포넌트가 브라우저 상에서 사라지기 전에 호출되는 메소드

 

샘플코드

생명주기 메소드를 적용한 샘플코드이다. 먼저 LifeCycle.js 파일을 아래와 같이 작성한다.

// 출처: https://github.com/velopert/learning-react 

import React, { Component } from 'react';

class LifeCycle extends Component {
  state = {
    num: 0,
    color: null,
  };

  myRef = null;

  // 생성자
  constructor(props) {
    super(props);
    console.log('constructor');
  }

  // props로 받아온 값을 state에 동기화 용도
  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps', nextProps, prevState);
    if (nextProps.color !== prevState.color) {
      return { color: nextProps.color };
    }
    return null;
  }

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

  // 컴포넌트가 리렌더링이 필요한지 결정, true/false 반환
  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate', nextProps, nextState);
    return nextState.num % 10 !== 4; // 마지막 자리가 4이면 리렌더링 하지 않음
  }

  componentWillUnmount() {
    console.log('componentWillUnmount');
  }

  // 업데이터 직전의 값을 참조할 경우 사용(ex. 스크롤바 위치 유지 등)
  getSnapshotBeforeUpdate(prevProps, prevSate) {
    console.log('getSnapshotBeforeUpdate', prevProps, prevSate, this.props);
    if (prevProps.color !== this.props.color) {
      return this.myRef.style.color;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate', prevProps, prevState);

    // snapshot: getSnapshotBeforeUpdate()의 리턴값
    if (snapshot) {
      console.log('업데이트 직전 색상: ', snapshot);
    }
  }

  // state 변경에 따른 컴포넌트 update 트리거
  handleClick = () => {
    this.setState({
      num: this.state.num + 1,
    });
  };

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

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

    return (
      <div>
        {this.props.missing.value}
        <h1 style={style} ref={(ref) => (this.myRef = ref)}>
          {this.state.num}
        </h1>
        <p>color: {this.state.color}</p>
        <button onClick={this.handleClick}>더하기</button>
      </div>
    );
  }
}

export default LifeCycle;

App.js파일은 다음과 같이 적용해준다. 

import LifeCycle from './LifeCycle';

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

  render() {
    return (
      <div>
          <LifeCycle color={this.state.color} />
      </div>
    );
  }
}
export default App;

위 코드를 실행하면 최초에는 마운트, 버튼을 누를 때 마다 업데이트를 수행한다. 생명주기 메소드 호출 로그는 브라우저 콘솔 로그를 통해서 확인 할 수 있다. 혹시 브라우저 콘솔 로그들이 2번씩 찍힐 경우에는 index.js에 React.StrictMode가 적용되어 있는지 확인해보고 있으면 제거를 해준다. 참고로 React.StrictMode가 적용되어 있어도 개발환경에서만 2번씩 호출이 되고, 빌드된 환경에서는 정상적으로 호출된다. 

 

componentDidCatch

추가적으로 위에서 언급하지 않는 componentDidCatch 메소드가 있는데, react v16부터 도입된 메소드로 컴포넌트 렌더링 중에 에러가 발생했을 경우 에러 UI를 보여 줄수 있게 해준다.

샘플 코드는 아래와 같이 ErrorBoundary.js을 추가 작성해준다.

// 출처: https://github.com/velopert/learning-react 

import React, { Component } from 'react';

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

  static getDerivedStateFromError(error) {
    console.log('getDerivedStateFromError', error);
    return { error };
  }
  componentDidCatch(error, info) {
    this.setState = {
      error: true,
    };
    console.log('componentDidCatch', { error, info });
  }

  render() {
    console.log(this.state.error);
    if (this.state.error) return <div>ERROR!!</div>;
    return this.props.children;
  }
}

export default ErrorBoundary;

 다음으로 App.js파일을 수정한다.

import LifeCycle from './LifeCycle';
import ErrorBoundary from './ErrorBoundary';

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

  render() {
    return (
      <div>
      	<ErrorBoundary>
          <LifeCycle color={this.state.color} />
        </ErrorBoundary>
      </div>
    );
  }
}
export default App;
 

그리고 위에서 작성한 LifeCycle.js에서 에러를 발생시킨다.

// 출처: https://github.com/velopert/learning-react 

import React, { Component } from 'react';

class LifeCycle extends Component {
   ...

  render() {
    ...

    return (
      <div>
        {this.props.missing.value} {/* 에러 빌셍 코드 추가 */}
        ...
      </div>
    );
  }
}

export default LifeCycle;

이제 브라우저에서 확인해보면 에러가 발생하고 ErrorBoundary.js파일에서 정의한 에러 화면이 렌더링된다.

 

 

반응형

댓글