리액트의 라이프 사이클과 useEffect

김형민·2021년 5월 30일
0

LifeCycle Method 는 한국어로 "생명주기 메서드" 라고 부릅니다. 생명주기 메서드는 컴포넌트가 브라우저상에 나타나고, 업데이트되고, 사라지게 될 때 호출되는 메서드들 입니다. 추가적으로 컴포넌트에서 에러가 났을 때 호출되는 메서드도 있습니다.

Mount

컴포넌트가 처음 실행될 때 그것을 Mount라고 표현합니다. 컴포넌트가 시작되면 우선 context, defaultProps와 state를 저장합니다. 그 후에 componentWillMount 메소드를 호출합니다. 그리고 render로 컴포넌트를 DOM에 부착한 후 Mount가 완료된 후 componentDidMount가 호출됩니다.

componentDidMount에서는 DOM에 접근할 수 있습니다. 그래서 여기에서는 주로 AJAX 요청을 하거나, setTimeout, setInterval같은 행동을 합니다.

정리하면 다음 순서로 실행됩니다.

  1. state, context, defaultProps 저장
  2. componentWillMount
  3. render
  4. componentDidMount

Props Update

props가 업데이트될 때의 과정입니다. 업데이트되기 전에 업데이트가 발생하였음을 감지하고, componentWillReceiveProps 메소드가 호출됩니다. 그 후 shouldComponentUpdate, componentWillUpdate가 차례대로 호출된 후, 업데이트가 완료(render)되면 componentDidUpdate가 됩니다. 이 메소드들은 첫 번째 인자로 바뀔 props에 대한 정보를 가지고 있습니다. componentDidUpdate만 이미 업데이트되었기 때문에 바뀌기 이전의 props에 대한 정보를 가지고 있습니다.

shouldcomponentUpdate에서는 아직 render하기 전이기 때문에 return false를 하면 render을 취소할 수 있습니다. 주로 여기서 성능 최적화를 합니다. 쓸데없는 update가 일어나면 여기서 걸러내는 거죠.

주의사항이 있는데, componentWillUpdate에서는 state를 바꿔서는 안 됩니다. 아직 props도 업데이트하지 않았으므로 state를 바꾸면 또 shouldComponentUpdate가 발생합니다. componentDidUpdate에서는 render이 완료되었기 때문에 DOM에 접근할 수 있습니다.

  1. componentWillReceiveProps
  2. shouldComponentUpdate
  3. componentWillUpdate
  4. render
  5. componentDidUpdate

함수형 컴포넌트에서도 이런 라이프사이클을 대체 할 수 있는 구현방법이 있을까?

useEffect는 함수형 컴포넌트에서 componentDidUpdate와 componentDidMount
componentWillUnmount를 대신한다고 봐도 좋을 것 같습니다.

componentDidMount는 컴포넌트가 화면에 나타나게 됐을 때 호출 되며 , 즉 render 후에 호출이 됩니다.
보통 컴포넌트에서 fetch,axios등을 통해 데이터를 받아오는데 사용 합니다.

componentDidUpdate는 컴포넌트 업데이트 직후에 호출 되는 함수입니다.
state가 업데이트 되고 그 후에 실행된다!

useEffect로 componentDidUpdate 작업하기

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  
  // useEffect는 첫번째 인자로 callBack함수를 받습니다.
  useEffect(() => {
    // 컴포넌트가 업데이트 되고 setTimeout함수를 실행합니다.
    setTimeout(() => {
    	document.title = `You clicked ${count} times`;
    }, 3000);   
  }, [count]); <------ 여기 집중!!!!!!!!!

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

위의 코드를 보면 useEffect함수 두번째 인자에 count를 배열안에 넣어주었습니다. 이렇게 하면 useEffect는 count state를 지켜보다가 count가 갱신되면 첫번째 인자로 넣어주었던 함수를 실행하게 됩니다.

이렇게 우리는 Class에서 다루었던 componentDidmount와 같은 동일한 작업을 Hook을통해 할수있습니다.

state가 업데이트 될때 실행

  • componentDidUpdate는 두번째 인자로 []빈 배열을 넣어주면 마운트되고 한번만 실행시킨다

useEffect로 componentWillUnmount 작업하기

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";

// Scroll을 움직이면 h1의 스타일을 변화해주기 위한 함수.
const useScroll = () => {
  // state를 생성합니다.
  const [state, setState] = useState({
    x: 0,
    y: 0
  });
  // scrll의 값을 가져와 state를 갱신합니다.
  const onScroll = () => {
    setState({ y: window.scrollY, x: window.scrollX });
  };
  useEffect(() => {
    // scroll 이벤트를 만들어줍니다. 스크롤을 움직일때 마다 
    // onScroll 함수가 실행됩니다.
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll); <---- 집중 !!!
  }, []);
  return state;
};

const App = () => {
  const { y } = useScroll();
  return (
    <div style={{ height: "1000vh" }} className="App">
      <h1 style={{ position: " fixed", color: y > 100 ? "red" : "blue" }}>
        Hi
      </h1>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

위의 코드에서 useEffect부분만 가져와서 자세히 살펴보겠습니다.

useEffect(() => {
    // scroll 이벤트를 만들어줍니다. 스크롤을 움직일때 마다 
    // onScroll 함수가 실행됩니다.
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll); <---- 집중 !!!
  }, []);
  return state;
};

우리는 scroll 이벤트를 만들었습니다. 그런데 만약 어떤 이유로 인해 컴포넌트가 지워진다면 우리는 해당 이벤트를 반드시 지워주어야 합니다.

그렇지 않으면 저 이벤트는 계속 남아있게 되어 원치않은 상황을 만들게 될것입니다. 그때 우리는 한번더 함수를 반환해줍니다.
이것이 바로 Class에서 사용했던 componentWillUnmount처럼 동일한 작업을 해줄겁니다.


정리

useEffect

componentDidMount 등 클래스 컴포넌트에 제공되었던 Life Cycle API는 useEffect로 사용할 수 있습니다. Life Cycle API에서 우리가 수행했던 API 요청, DOM 조작 등이 side effect이기 때문에, useEffect라는 이름의 API가 되었습니다.
클래스 컴포넌트에서의 componentDidMount, componentDidUpdate, componentWillUnmount가 useEffect로 실행됩니다.
function useEffect(effect: EffectCallback, inputs?: InputIdentityList)
render가 발생할 때 마다(componentDidMount: 초기, componentDUpdate: 매번) effect가 실행됩니다. 두 번째 파라미터인 inputs를 통해 특정한 상태가 update 되었을 때만 effect가 실행되도록 설정할 수 있습니다.

componentDidMount - 최초한번
componentDUpdate - 상태값이 변할때마다
componentWillUnmount - return 값이 있는 경우 hook의 cleanup 함수로 인식하고 다음 effect가 실행되기 전에 실행

import { useState, useEffect } from 'react';

export function Data() {
  const [data, setData] = useState(null);

  useEffect(() => {
    API.getData()
      .then((response) => { setData(response) });
  }, []);

  const isLoading = (data == null);
  if (isLoading) {
    return 'Loading..';
  }
  return data;
}

위 예제는 useEffect의 inputs에 빈 배열을 넘겨서 최초(componentDidMount)에만 실행되도록 하였습니다. useEffect는 여러 개 사용할 수 있기 때문에 각 state마다 정의해 줄 수도 있고, 예제처럼 최초에 실행되는 것만 정의해주어도 됩니다.
componentDidMount와 componentDidUpdate의 실행방법은 알겠는데, 그럼 componentWillUnmount는 어떻게 실행할까요?
useEffect(() => {
window.addEventListener("mousemove", logMousePosition);
return () => {
window.removeEventListener("mousemove", logMousePosition);
};
}, []);
effect 함수의 return 값이 있는 경우 hook의 cleanup 함수로 인식하고 다음 effect가 실행되기 전에 실행해줍니다. componentWillUnmount는 컴포넌트가 사라지기 전에 한 번만 실행했지만, cleanup 함수는 새로운 effect 실행 전에 매번 호출된다는 차이가 있습니다.
위 예제코드에서는 inputs로 빈 배열을 넘겨주었기 때문에 unmount 될 때 한 번만 실행됩니다.

profile
항해 중인 개발자

0개의 댓글