React useEffect 후크

useEffect Hook을 사용하면 컴포넌트에서 side effect를 수행할 수 있습니다.

side effect의 예로는 데이터 가져오기, DOM 직접 업데이트, 타이머 등이 있습니다.

useEffect에는 두 개의 인수를 사용할 수 있습니다. 두 번째 인수는 옵션입니다.

useEffect(<function>, <dependency>)

예시로 타이머를 사용해 봅시다.

예제 1 - set Timeout()을 사용하여 초기 렌더링 후 1초를 카운트합니다.
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  });

  return <h1>I've rendered {count} times!</h1>;
}

ReactDOM.render(<Timer />, document.getElementById('root'));

그런데 잠깐!! 한 번만 세야 하는데 계속 세고 있습니다!

useEffect는 모든 렌더에서 실행됩니다. 즉, 카운트가 변경되면 렌더가 발생하며, 그 결과 다른 effect가 트리거됩니다.

이건 우리가 원하는게 아닙니다. side effect 시기를 조절하는 몇 가지 방법이 있다.

배열을 받아들이는 두 번째 매개 변수를 항상 포함해야 합니다. 옵션으로 종속성을 전달하여 이 배열에서 useEffect를 사용할 수 있습니다.

1. 종속성이 전달되지 않음
useEffect(() => {
  // 모든 랜더에서 실행됨
});
2. 빈 배열
useEffect(() => {
  // 첫번째 랜더에서만 실행됨
}, []);
3. props나 state 값
useEffect(() => {
  // 첫번째 랜더에서 실행됨
  // props나 state가 변경될때 실행됨
}, [prop, state]);

이 문제를 해결하기위해 초기 렌더링에서만 이 effect를 실행해 보겠습니다.

예제 2 - 초기 렌더링에만 effect를 실행합니다.
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []); // <- add empty brackets here

  return <h1>I've rendered {count} times!</h1>;
}

ReactDOM.render(<Timer />, document.getElementById('root'));
예제 3 - 다음은 변수에 종속된 useEffect Hook의 예입니다. 카운트 변수가 업데이트되면 효과가 다시 실행됩니다.
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Counter() {
  const [count, setCount] = useState(0);
  const [calculation, setCalculation] = useState(0);

  useEffect(() => {
    setCalculation(() => count * 2);
  }, [count]); // <- add the count variable here

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>Calculation: {calculation}</p>
    </>
  );
}

ReactDOM.render(<Counter />, document.getElementById('root'));

종속성이 여러 개인 경우 useEffect 종속성 배열에 포함해야 합니다.

effect cleeanup

일부 effect는 메모리 누수를 줄이기 위해 cleanup이 필요합니다.

타임아웃, 서브스크립션, 이벤트 리스너 및 더 이상 필요하지 않은 기타 effect는 폐기해야 합니다.

이를 위해 useEffect Hook 끝에 반환 함수를 포함합니다.

예제 4 - useEffect Hook 끝의 타이머를 cleanup 합니다.
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setTimeout(() => {
    setCount((count) => count + 1);
  }, 1000);

  return () => clearTimeout(timer)
  }, []);

  return <h1>I've rendered {count} times!</h1>;
}

ReactDOM.render(<Timer />, document.getElementById("root"));

Note: 타이머를 클리어하기 위해서 이름을 붙여야 합니다.