React/React 문법

[React]클래스 컴포넌트 생명주기 Hook으로 변경

DevStory 2021. 9. 29.

이번 포스팅에서는 React에서 클래스 컴포넌트의 생명주기(LifeCycle)를 React Hook으로 변경하는 방법을 설명합니다.

 


React 생명 주기

기본적인 React 생명 주기는 [마운팅(Mountin) → 업데이트(Update) → 마운트 해제(Unmounting)] 과정을 거치며, 클래스 컴포넌트에서는 각 과정을 담당하는 생명주기 메서드가 있습니다.

 

componentDidMount

  • 컴포넌트가 마운트 된 후 호출됩니다.
  • 브라우저에서 한 번만 실행됩니다.
  • axios, fetch 등을 사용하여 외부 라이브러리 연동 및 네트워크 요청을 보내는데 적절합니다.

 

componentDidUpdate

  • 컴포넌트가 갱신된 후 호출됩니다.
  • 즉, 컴포넌트의 state가 변경되었을 경우 실행됩니다.
  • 갱신이 일어난 후 호출되므로 최초 렌더링에서는 호출되지 않습니다. 

 

componentWillUnmount

  • 컴포넌트가 마운트 해제되어 제거되기 직전에 호출됩니다.
  • 컴포넌트가 마운트 해제되면 다시 렌더링 되지 않으므로 state를 변경하는 setState()를 사용해서는 안됩니다.

 

위 3개의 생명 주기 이외에도 더 이상 사용하면 안 되는 UNSAFE_componentWillUpdate, UNSafe_componentWillReceiveProps 그리고 잘 사용하지 않는 shouldComponentUpdate, getDerviedStateFromProps... 등 여러 개의 생명주기 메서드가 존재합니다.

 

하지만, React Hook은 자주 사용되는 그리고 꼭 필요한 생명주기 메서드인 componentDidMount, componentDidUpdate, componentWillUnmount를 하나의 API인 useEffect로 통합하였습니다.

 

다음은 useEffect에서 클래스 컴포넌트의 생명 주기 메서드 componetDidMount, componentDidUpdate, componentWillUnmount를 구현해보겠습니다.


React Hook에서 componentDidMount

다음 코드는 클래스 컴포넌트에서 componentDidMount를 사용한 코드입니다.

import React from "react";

class App extends React.Component {
    componentDidMount() {
        console.log('App componentDidMount()');
    }
    
    render() {
        return <h1>App Component Life Cycle</h1>;
    }
}

 

다음 코드는 React Hook에서 componentDidMount를 사용한 코드입니다.

import React, { useEffect } from "react";

const App = () => {
    useEffect(() => {
        console.log('App componentDidMount()');
    }, []);
    
    return <h1>App Component Life Cycle</h1>;
}

useEffect()에 두 번째 매개변수로 빈 배열( [] )을 전달하여 컴포넌트가 마운트된 직후 한 번만 실행됩니다.


React Hook에서 componentDidUpdate

다음 코드는 클래스 컴포넌트에서 componentDidUpdate를 사용한 코드입니다.

import React from "react";

class App extends React.Component {
    componentDidUpdate() {
        console.log('App componentDidUpdate()');
    }
    
    render() {
        return <h1>App Component Life Cycle</h1>;
    }
}

 

다음 코드는 React Hook에서 componentDidUpdate를 사용한 코드입니다.

import React, { useEffect } from "react";

const App = () => {
    useEffect(() => {
        return() => {
            console.log('App componentWillUnmount()');
        }
    });
    
    return <h1>App Component Life Cycle</h1>;
}

componentDidMount와 유사하지만, 차이점으로는 useEffect()에 두 번째 매개변수가 존재하지 않다는 점입니다.

 

즉, 위에서 작성한 useEffect는 다음 두 가지 경우 실행됩니다.

  • 컴포넌트가 마운트된 직후 한 번 실행됩니다.
  • 컴포넌트의 요소가 변경되면 실행됩니다.

특정 state가 변경되었을 경우에만 useEffect가 호출되게 하려면 다음과 같이 작성할 수 있습니다.

 

다음 코드는 클래스 컴포넌트에서 this.state.text가 변경되었을 경우 componentDidUpdate에서 특정 로직이 실행되도록 하는 코드입니다.

import React from "react";

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            text: ""
        };
    }

    componentDidUpdate(prevProps, prevState) {
        if(prevState.text !== this.state.text) {
            // this.state.text가 변경되었을 경우에만 특정 로직 실행
            console.log('this.state.text가 변경되었을 경우');
        }
    }
    
    render() {
        return <h1>App Component Life Cycle</h1>;
    }
}

componentDidUpdate 생명주기 메서드에서 두 번째 인수인 prevState와 this.state를 비교하여 특정 state가 변경되었을 경우 로직을 실행하도록 코드를 구현할 수 있습니다.

 

이러한 방식은 특정 state가 변경되었을 경우 실행해야 하는 로직이 많을수록 if문이 길어진다는 단점이 있습니다.

 

다음 코드는 React Hook에서 특정 state가 변경되었을 경우 useEffect에서 특정 로직이 실행되도록 하는 코드입니다.

import React, { useEffect } from "react";

const App = () => {
    const [text, setText] = useState("");
    
    useEffect(() => {
        // text가 변경되었을 경우에만 특정 로직 실행
        console.log("text가 변경되었을 경우");
    }, [text]);
    
    return <h1>App Component Life Cycle</h1>;
}

두 번째 매개변수 빈 배열( [] )에 특정 state가 변경되었을 경우 실행되도록 특정 state이름을 빈 배열에 작성합니다.

 

빈 배열( [] )에 특정 state이름을 작성하여 특정 state가 변경되었을 경우 useEffect가 실행되도록 했지만, 여전히 컴포넌트가 마운트된 직후 한 번 실행된다는 문제가 남아있습니다.

 

구글링 하면 useRef를 사용하여 해결하면 된다는 글이 많은데, useRef는 100% 보장해주지 않습니다.

 

다음 코드는 App 컴포넌트가 마운트 될 때 console.log("text가 변경되었을 경우")가 실행되지 않습니다.

import React, { useEffect } from "react";

const App = () => {
    const [text, setText] = useState("");
    const isRef = useRef(false);
    
    useEffect(() => {
        if(isRef.current) {
            console.log("text가 변경되었을 경우");
        } else {
            isRef.current = true;
        }
    }, [text]);
    
    return <h1>App Component Life Cycle</h1>;
}

 

하지만, 다음 코드처럼 useEffect가 여러 개인 경우 문제가 발생합니다.

import React, { useEffect } from "react";

const App = () => {
    const [text1, setText1] = useState("");
    const [text2, setText2] = useState("");
    const isRef = useRef(false);
    
    useEffect(() => {
        if(isRef.current) {
            console.log("text1가 변경되었을 경우");
        } else {
            isRef.current = true;
        }
    }, [text1]);
    
    useEffect(() => {
        if(isRef.current) {
            console.log("text2가 변경되었을 경우");
        } else {
            isRef.current = true;
        }
    }, [text2]);
    
    return <h1>App Component Life Cycle</h1>;
}

컴포넌트가 마운트 될 때, 특정 useEffect가 실행되지 않도록 하는 방법은 내용이 길어질 거 같아 따로 포스팅하도록 하겠습니다.


React Hook에서 componentWillUnmount

다음 코드는 클래스 컴포넌트에서 componentWillUnmount를 사용한 코드입니다.

import React from "react";

class App extends React.Component {
    componentWillUnmount() {
        console.log('App componentWillUnmount()');
    }
    
    render() {
        return <h1>App Component Life Cycle</h1>;
    }
}

 

다음 코드는 React Hook에서 componentWillUnmount를 사용한 코드입니다.

import React, { useEffect } from "react";

const App = () => {
    useEffect(() => {
        return() => {
            console.log('App componentWillUnmount()');
        }
    }, []);
    
    return <h1>App Component Life Cycle</h1>;
}

componentDidMount와 유사하지만, 차이점으로는 useEffect 내부에 return문이 존재합니다.

 

useEffect 내부의 return문은 해당 컴포넌트가 DOM에서 제거될 때만 호출됩니다.


예제 코드

다음은 제가 CodeSandBox에서 작성한 코드입니다.

라디오 버튼에서 Y를 선택하면, Child 컴포넌트가 마운팅 되고 N을 선택하면 마운팅 해제됩니다.

 

텍스트 박스에 값을 입력하면 Child 컴포넌트의 state가 변경됩니다.

 

콘솔창에서 생명주기가 동작하는 과정을 확인할 수 있습니다.

 

☞ React 클래스 컴포넌트 생명주기

☞ React Hook 생명주기

 

반응형

댓글