책 읽다가 코딩하다 죽을래

React 컴포넌트의 생명주기를 실습으로 알아보기 (componentDidMount, componentDidUpdate, componentWillUnmount) 본문

코딩/react

React 컴포넌트의 생명주기를 실습으로 알아보기 (componentDidMount, componentDidUpdate, componentWillUnmount)

ABlue 2021. 6. 28. 20:57

모든 생명은 탄생하며 시간이 지나면 죽게 된다.

객체 프로그래밍의 객체들도 똑같다.

생성자를 통하여 필요한 메모리를 할당하고

객체의 역할이 끝났으면

소멸자를 통하여 메모리를 반환한다.

 

컴퓨터의 자원은 한정적이라 역할이 끝난 모든 것들은 메모리를 반환해야

메모리 누수 문제가 생기지 않으며 더 좋은 성능을 발휘할 수 있다.

 

컴포넌트형 프로그래밍 React에서도 마찬가지이다.

컴포넌트의 생기는 시점에 메모리를 할당받게 되고

역할이 끝나 사라지는 시점에는 메모리를 반환해야 한다.

 

이러한 컴포넌트의 생명주기를 다루는 것이 바로 

componentDidMount, componentDidUpdate, componentWillUnmount 이다

 

componentDidMount은 컴포넌트가 트리에 삽입된 직후에 호출되며(컴포넌트가 생성이 될 때),

componentDidUpdate 컴포넌트가 갱신이 일어난 직후에 호출되며(state 변화가 있을 때, 최초 생성 시엔 호출되지 않음),

componentWillUnmount은 컴포넌트가 제거되기 직전에 호출된다.

 

호출이라는 말을 사용했으니 이 3가지는 함수라는 것을 눈치챘을 것이다.

즉 componentDidMount는 컴포넌트가 생성이 될 때 해야할 동작(타이머 시작, 네트워크 요청),

componentDidUpdate는 state나 리랜더링이 일어났을 때 해야할 동작,

componentWillUnmount는 컴포넌트가 제거되었을 때 해야할 동작(타이머 제거, 네트워크 요청 취소)을

기술하면 될 것이다. 

 

요번 시간에는 이 3가지 함수를 복권번호뽑기 게임을 통해 알아볼 것이다.

코드는 제가 다 짜놨으니 복붙만 하시면 됩니다.

 

import React, { Component } from 'react'
import Child from './Child'

function getRandomNumber() {
    console.log("getRandomNumber함수에 들어왔습니다.");
    let numbersOfSet = new Set();
    while(numbersOfSet.size < 7){
        const randomNumbers = Math.floor(Math.random() * 45);
        if(!(numbersOfSet.has(randomNumbers))){
            numbersOfSet.add(randomNumbers);
        }

    }
    return [...numbersOfSet].sort((a,b) => a - b);
}

class Lotto extends Component{
    state = {
        Numbers : getRandomNumber(),
        LottoNumber : [],
        nextIndex : 0,
        bonusNumberTurn : false,

    }
    componentDidMount(){
        console.log("부모 컴포넌트가 새롭게 시작되었습니다.");
    }
    componentDidUpdate(){
        console.log("부모 컴포넌트가 다시 시작되었습니다.");
    }
    componentWillUnmount(){

    }

    nextNumber = () => {
        if(this.state.nextIndex < 6){
            this.setState((prevState) => {
                return {
                    LottoNumber : [...prevState.LottoNumber, " " ,this.state.Numbers[prevState.nextIndex]],                
                    nextIndex : prevState.nextIndex + 1,
                }
            })
        } else {
            this.setState({
                bonusNumberTurn : true,
            })
        }
    }
    reset = () => {
        this.setState({
            Numbers : getRandomNumber(),
            LottoNumber : [],
            nextIndex : 0,
            bonusNumberTurn : false,
        })
    }
    render(){
        const { Numbers, LottoNumber,bonusNumberTurn } = this.state;
        return (
            <>
                {LottoNumber}
                <button onClick={this.nextNumber}>다음 번호</button>
                {bonusNumberTurn && 
                <div>
                    <Child BonusNumber={Numbers[6]}/>
                    <button onClick={this.reset}> 다시 시작</button>
                </div>}
            </>
        )
    }
}

export default Lotto

부모 컴포넌트

import React, { Component } from 'react'

class Child extends Component{
    state = {

    }
    componentDidMount(){
        console.log("자식 컴포넌트가 새롭게 시작되었습니다.");
    }
    componentDidUpdate(){
        console.log("자식 컴포넌트가 다시 시작되었습니다.");
    }
    componentWillUnmount(){
        console.log("자식 컴포넌트가 끝났습니다.");

    }
    
    render(){
        return (
            <div>
                보너스 번호 : {this.props.BonusNumber}
            </div>
        )

    }
}

export default Child

자식 컴포넌트

 

결과물

 

여기서 6개의 당첨번호를 출력하는 것이 부모 컴포넌트이며

마지막 보너스 번호를 출력하는 것이 자식 컴포넌트이다.

 

이 코드의 전부를 다 해석할 필요는 없다.

우리는 이번 시간에

componentDidMount, componentDidUpdate, componentWillUnmount

이 3가지 함수에 호출 타이밍만 알아도 귀중한 것을 알아간 것이다.

 

이 코드에서 3가지 함수를 잘 보시면 호출타이밍을 알 수 있게 console.log 함수를 넣어놨다.

이제 이 코드를 실행하고 Ctrl + Shift + J 키를 눌러보자

 

 

코드 실행 시

 

보시면 가장 먼저 나온 메세지는 무엇인지를 보자

"getRandomNumber함수에 들어왔습니다" 다음에

"부모 컴포넌트가 새롭게 시작되었습니다." 이다.

(영어로 나오는 로그들은 필자가 쓰는 dev 서버 관련된 로그이니 신경쓰지말자)

 

componentDidMount함수보다 클래스의 getRandomNumber함수를 먼저 시작되었다는 것이다.

즉 컴포넌트가 생성되면 state 즉 랜더링부터 시작하며 그 다음에 componentDidMount가 실행되는 것이다.

 

자 그럼 다음 번호를 눌러보자

 

 

 

다음 번호가 나오며 콘솔에는

"부모 컴포넌트가 다시 시작되었습니다" 라는 메세지가 나왔다.

 

그렇다. componentDidUpdate함수는

최초 실행시에는 호출되지 않으며 setState같은 요소로 컴포넌트가 리랜더링이 일어나면 호출이 된다.

 

그럼 다음 번호 버튼을 5번 더 눌러보자

 

"부모 컴포넌트가 다시 시작되었습니다" 라는 메세지가 5번 나왔다.

리랜더링이 일어날때마다 componentDidUpdate 계속 호출이 된다는 것이다.

 

그럼 이제 자식 컴포넌트가 나올 차례이다.

다음 번호 버튼을 한 번 더 눌러보자.

 

자식 컴포넌트가 등장했으니

"자식 컴포넌트가 새롭게 시작되었습니다."가 메세지가 나왔다.

자식의 componentDidMount 함수가 호출된 것이다.

 

그리고 부모에서 자식컴포넌트를 불러왔으니 부모에서도 리랜더링이 일어나 componentDidUpdate함수가 호출된 것이다.

 

이제 복권번호를 다 보았으니 자식 컴포넌트를 제거할 차례이다.

다시 시작 버튼을 눌러보자.

 

다시 시작 버튼을 눌러 복권번호를 다시 뽑게 되었으니

getRandomNumber 함수가 호출되고

자식 컴포넌트가 사라져 자식의 componentWillUnmount 함수를 호출해

"자식 컴포넌트가 끝났습니다." 메세지가 나왔다.

이번에도 역시 자식 컴포넌트가 제거되어 부모 컴포넌트가 리랜더링 되었으므로 부모 컴포넌트의 componentDidUpdate 함수가 호출된 것이다.

 

이제 타이밍을 정리해보자면

컴포넌트는 생성 -> state -> render -> componentDidMout

 => (setState/props 바뀔때) -> render -> componentDidUpdate

부모컴포넌트에 의해 자식컴포넌트를 제거될 때 -> componentWillUpmount => 소멸

 

이 순서를 따르게 된다.

 

이번 시간에는 이것만 알아도 보람찬 하루가 되겠지만

componentDidUpdate 의 주의점 하나만 알고 가자

 

 

componentDidUpdate(prevProps) {
  // 전형적인 사용 사례 (props 비교를 잊지 마세요)
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

 

componentDidUpdate 함수에서 setState를 불러올 수 있지만

위의 예시처럼 조건문으로 감싸야 한다.

왜냐하면 setState는 리랜더링을 불러오고 리랜더링은 componentDidUpdate 함수를 부르게 된다.

즉 무한 반복이 발생할 수 있다.