본문 바로가기
JavaScript/React

React) 컴포넌트의 라이프사이클 메소드

by 박채니 2022. 11. 14.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

 

컴포넌트의 라이프사이클 메소드

모든 리액트 컴포넌트에는 라이프사이클 (생명 주기)이 존재합니다.

프로젝트 진행 시 컴포넌트를 처음으로 렌더링할 때 어떠한 작업을 처리하거나, 업데이트 전 후로 어떠한 작업을 처리해야 할 수도, 불필요한 업데이트를 방지해야 할 수도 있습니다.

이 때 라이프사이클 메소드를 이용하며, 클래스형 컴포넌트에서만 사용할 수 있습니다.

(함수형은 Hooks 기능을 이용해 비슷하게 처리)

 


라이프사이클 메소드의 이해

- Will 접두사 : 어떤 작업을 작동하기 에 실행되는 메소드

- Did 접두사 : 어떤 작업을 작동한 에 실행되는 메소드

이러한 총 9가지의 메소드들을 컴포넌트 클래스에서 덮어 써 선언하여 사용합니다.

 

라이플 사이클은 총 3가지로 나뉩니다.

  • 마운트
  • 업데이트
  • 언마운트

 

마운트

- DOM이 생성되고 웹 브라우저상에 나타나는 것으로, 호출 메소드는 아래와 같습니다.

  • constructor : 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메소드
  • getDerivedStateFromProps : props에 있는 값을 state에 넣을 때 사용하는 메소드
  • render : 준비한 UI를 렌더링하는 메소드
  • componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메소드

 

업데이트

- 컴포넌트는 총 네 가지 경우에 업데이트를 실행합니다.

  1. props가 바뀔 때 (부모 컴포넌트에서 넘겨주는 props가 변경)
  2. state가 바뀔 때 (자신이 들고 있는 state가 setState를 통해 업데이트)
  3. 부모 컴포넌트가 리렌더링될 때 (자신에게 할당된 props가 바뀌지 않아도, 자신의 state가 바뀌지 않아도 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 리렌더링)
  4. this.forceUpdate로 강제로 렌더링을 트리거 할 때

 

  • getDerivedStateFromProps : 마운트 과정에서도 호출되며, 업데이트가 시작하기 전에도 호출되는 메소드
    props의 변화에 따라 state 값에도 변화를 주고 싶을 때 사용
  • shouldComponentUpdate : 컴포넌트가 리렌더링을 해야 할지 말아야 할지를 결정하는 메소드
    true 혹은 false를 반환하며, true 반환 시 다음 메소드를 계속 실행하고 false 반환 시 작업을 중지(컴포넌트 리렌더링 X)
    만일, this.forceUpdate() 함수 호출 시 이 과정을 생략하고 render 함수 호출
  • render : 컴포넌트 리렌더링
  • getSnapshotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출하는 메소드
  • componentDidUpdate : 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메소드

 

언마운트

- 마운트의 반대 과정, 컴포넌트를 DOM에서 제거하는 것

  • componentWillUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출하는 메소드

 


라이프사이클 메소드 살펴보기

 

render() 함수

 

render() { ... }

컴포넌트의 모양새를 정의하며, 가장 중요한 메소드로 유일한 필수 메소드입니다.

render 함수 안에서 this.props와 this.state에 접근 가능하고, 리액트 요소를 반환합니다. (div 같은 태그, 컴포넌트 혹은 null 값 / false로 아무것도 보여주지 않음)

해당 함수 안에서는 이벤트 설정이 아닌 곳에서 setState 사용 혹은 브라우저의 DOM에 접근하면 안됩니다.

(DOM 정보를 가져오거나 state 변화를 줄 때는 componentDidMount에 처리!)

 

constructor 메소드

 

constructor(props) { ... }

컴포넌트의 생성자 메소드로 컴포넌트를 만들 때 처음으로 실행되며, 초기 state를 정할 수 있습니다.

 

getDerivedStateFromProps 메소드

 

static getDerviedStateFromProps(nextProps, prevState) {
	if(nextProps.value !== prevState.value) { // 조건에 따라 특정 값 동기화
    	return { value: nextProps.value };
    }
    return null; // state를 변경할 필요가 없다면 null 반환
}

props로 받아 온 값을 state에 동기화시키는 용도로 사용하며, 컴포넌트가 마운트될 때업데이트될 때 호출됩니다.

 

componentDidMount 메소드

 

componentDidMount() { ... }

컴포넌트를 만들고, 첫 렌더링을 다 마친 후 실행됩니다.

해당 메소드 안에서 다른 자바스크립트 라이브러리 또는 프레임워크의 함수를 호출, 이벤트 등록, setTimeout, setInterval, 네트워크 요청 같은 비동기 작업을 처리합니다.

 

shouldComponentUpdate 메소드

 

shouldComponentUpdate(nextProps, nextState) { ... }

props 또는 state를 변경했을 때, 리렌더링을 시작할지 여부를 지정하는 메소드입니다.

반드시 true 혹은 false를 반환하며, 기본 값은 true입니다. (false 반환 시 중지)

해당 메소드 안에서 현재 props와 state는 this.props와 this.state로 접근하며, 새로 설정될 props 또는 state는 nextProps와 nextState로 접근할 수 있습니다.

프로젝트 성능을 최적화할 때, 상황에 맞는 알고리즘을 작성하여 리렌더링을 방지할 때는 false값을 반환!

 

getSnapshotBeforeUpdate 메소드

 

getSnapshotBeforeUpdate(prevProps, prevState) {
	if(prevState.array != this.state.array) {
    	const { scrollTop, scrollHeight } = this.list;
        return { scrollTop, scrollHeight };
    }
}

render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출됩니다.

해당 메소드에서 반환하는 값componentDidUpdate에서 세 번째 파라미터(snapshot) 값으로 전달 받을 수 있습니다.

주로 업데이트하기 직전의 값을 참고할 일이 있을 때 활용 (ex. 스크롤바 위치 유지 ...)

 

componentDidUpdate 메소드

 

componentDidUpdate(prevPros, prevState, snapshot) { ... }

리렌더링을 완료한 후 실행하며, 업데이트가 끝난 직후이므로 DOM 관련 처리를 해도 괜찮습니다.

prevPros 혹은 prevState로 컴포넌트가 이전에 가졌던 데이터에 접근할 수 있습니다.

또한 getSnapshotBeforeUpdate에서 반환한 값이 있다면 snapshot 값을 전달 받을 수 있습니다.

 

componentWillUnmount 메소드

 

componentWillUnmount() { ... }

컴포넌트를 DOM에서 제거할 때 실행합니다.

componentDidMount에서 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 해당 메소드에서 제거 작업을 해야 합니다.

 

componentDidCatch 메소드

 

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

컴포넌트 렌더링 도중 에러 발생 시 애플리케이션이 먹통되지 않고 오류 UI를 보여줄 수 있게 해줍니다.

error파라미터에 어떤 에러가 발생했는 지 알려주고, info 파라미터는 어디에 있는 코드에서 오류가 발생했는지 알려줍니다.

하지만 컴포넌트 자신에게 발생하는 에러를 잡아낼 수 없고, 자신의 this.props.children으로 전달되는 컴포넌트에서 발생하는 에러만 잡아낼 수 있습니다.

 


라이프사이클 메소드 사용하기

 

예제 컴포넌트 생성

 

LifeCycleSample.js

import { Component } from "react";

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

    myRef = null; // ref를 설정할 부분

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

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

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

    shouldComponentUpdate(nextProps, nextState) {
        console.log('shouldComponentUpdate', nextProps, nextState);
        // 숫자의 마지막 자리가 4면 리렌더링하지 않음
        return nextState.number % 10 !== 4;
    }

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

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

    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log('getSnapshotBeforeUpdate');
        if(prevProps.color !== this.props.color) {
            return this.myRef.style.color;
        }
        return null;
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('componentDidUpdate', prevProps, prevState);
        if(snapshot) {
            console.log('업데이트되기 직전 색상 : ', snapshot);
        }
    }

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

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

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

}

export default LifeCycleSample;

각 메소드 실행 시 콘솔 디버거에 기록하고, 부모 컴포넌트에서 props로 색상을 받아 버튼을 누르면 state.number 값을 1씩 증가시켜줍니다.

getDerivedStateFromProps부모에게서 받은 color 값을 state에 동기화 시키고 있는 것을 확인할 수 있습니다.

getSnapshotBeforeUpdateDOM에 변화가 일어나기 직전색상 속성을 snapshot 값으로 반환하여 해당 값을 componentDidUpdate에서 조회할 수 있게 하였습니다.

또한, shouldComponentUpdate 메소드에서 state.number 값의 마지막 자리 수가 4면 리렌더링을 취소하도록 하였습니다.

 

App 컴포넌트에서 예제 컴포넌트 사용

 

App.js

import { Component } from 'react';
import './App.css';
import LifeCycleSample from './LifeCycleSample';

// 랜덤색상 생성
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}>랜덤 색상</button>
        <LifeCycleSample color={this.state.color} />
      </div>
    )
  }
}

export default App;

getRandomColor 함수는 state의 color 값을 랜덤 색상으로 설정합니다.

16777215를 hex로 표현 → ffffff 이므로, 000000부터 ffffff 값을 반환하게 됩니다.

버튼을 누르면 handleClick 메소드가 호출되고, LifeCycleSample 컴포넌트의 color 값을 props로 설정합니다.

 

처음 렌더링 시

※ 콘솔 값이 2번씩 출력됐는데 이는 React.StricMode 때문 적용되어 있어서 그럼

개발 환경에서만 두 번씩 호출되고, 프로덕션 환경에선 정상 호출되지만, 불편하다면 index.js에서 React.StrictMode를 제거

 

랜덤 색상 버튼 클릭 시
더하기 버튼 클릭 시
마지막 자리 수가 4일 경우 (업데이트 취소)

 

에러 잡아내기

 

일부로 에러를 발생 시켜 보겠습니다.

 

LifeCycleSample.js - return

    return(
        <div>
            {this.props.missing.value} {/* 오류 발생 코드 */}
            <h1 style={style} ref={(ref) => this.myRef = ref}>{this.state.number}</h1>
            <p>color : {this.state.color}</p>
            <button onClick={this.handleClick}>더하기</button>
        </div>
    )

오류가 발생하였고, 화면은 아무것도 나타나지 않는 빈 화면이 렌더링 되는 것을 확인할 수 있습니다.

 

에러를 잡아주는 ErrorBoundary 컴포넌트를 생성하여 에러가 발생하였다는 것을 사용자에게 알려주도록 하겠습니다.

 

ErrorBoundary.js

import { Component } from "react";

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

    componentDidCatch(error, info) {
        this.setState({
            error: true
        });
        console.log({ error, info });
    }
    
    render() {
        if(this.state.error) return <div>에러가 발생하였습니다!</div>;
        return this.props.children;
    }
}

export default ErrorBoundary;

 

App.js - render 함수

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>랜덤 색상</button>
        <ErrorBoundary>
          <LifeCycleSample color={this.state.color} />
        </ErrorBoundary>
      </div>
    )
  }

 

에러 발생 시 componentDidCatch 메소드가 호출되고, state.error 값을 true로 업데이트 해주었습니다.

render 함수에서 에러발생 시 '에러가 발생했습니다!' 문구가 출력되도록 하였고 렌더링이 잘 되는 것을 확인할 수 있습니다.