블로그

[토스트] 리액트 useEffect: 개발자가 알아야 할 네가지 팁을 읽고 본문

아티클

[토스트] 리액트 useEffect: 개발자가 알아야 할 네가지 팁을 읽고

wooluck 2021. 4. 8. 21:52
리액트 useEffect: 개발자가 알아야 할 네가지 팁
React useEffect: 4 Tips Every Developer Should Know

 

리액트 Hook에서 useEffect 함수를 어떻게 작성하면 좋을 지에 대해 짤막한 네 개의 팁을 공유하는 포스팅이다.

 

1. 단일 목적의 useEffect

먼저 useEffect는 단일 목적의 함수로 분리해야 한다.

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);

  // 이렇게 하면 안된다!
  useEffect(() => {
    const timeoutA = setTimeout(() => setVarA(varA + 1), 1000);
    const timeoutB = setTimeout(() => setVarB(varB + 2), 2000);

    return () => {
      clearTimeout(timeoutA);
      clearTimeout(timeoutB);
    };
  }, [varA, varB]);

  return (
    <span>
      Var A: {varA}, Var B: {varB}
    </span>
  );
}

위의 코드는 한 번의 상태 변경으로 여러 상태 변수에 업데이트가 발생하게 된다.

 

포스팅에는 정리되어 있지 않지만 이유는 아래와 같다.

위의 코드에서 Effect Hook은 두 값 중 하나가 변경되었을 때, cleanup 함수를 호출한다.

varA와 varB의 변경은 setTimeout으로 처리되며, setTimeout의 콜백은 매크로 태스크 큐에 등록된다.

매크로 태스크 큐는 FIFO의 구조를 가지므로 먼저 큐에 추가된 timeoutA가 먼저 콜 스택으로 이동하게 된다.

이후 varA의 변화가 먼저 일어나며, Effect Hook은 cleanup 함수를 호출하게 되므로 매크로 태스크 큐에 존재하는 timeoutB는 clearTimeout 처리가 된다.

그러므로 실제 실행 시 Var A만 변경된다.

 

이를 방지하기 위해서는 useEffect를 분리해야 한다.

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);
    return () => clearTimeout(timeout);
  }, [varB]);

2. 가능하다면 커스텀 훅을 사용한다

포스팅의 내용대로 커스텀 훅을 사용하게 되면 코드가 약간이나마 길어지긴 하지만 명확하게 분리된 함수를 구현할 수 있다.


3. 조건부 useEffect의 옳은 방법

특정 기준 까지만 동작해야 할 때, cleanup이 실행되도록 처리하기보다 기준을 넘어섰으면 즉시 return하는게 좋다.

실제로 큰 문제는 생기지 않지만 가독성 측면에서 매우 깔끔해보인다.

ps에서 예외처리를 최상단에 해주는 느낌?

// 나쁜 방법, 오류는 없음.
useEffect(() => {
  let timeout;
  if (varA < 5) {
    timeout = setTimeout(() => setVarA(varA + 1), 1000);
  }
  // 매번 cleanup 함수가 호출
  return () => clearTimeout(timeout);
}, [varA]);
  
  
// 좋은 방법
useEffect(() => {
  // 기준이 넘으면 즉시 종료
  if (varA >= 5) return;
  const timeout = setTimeout(() => setVarA(varA + 1), 1000);
  return () => clearTimeout(timeout);
}, [varA]);

4. useEffect 안에서 사용하는 모든 변수들을 디펜던시 배열에 추가한다

useEffect의 두 번째 파라미터인 디펜던시 배열에 내부 동작에서 사용하는(조회, 수정 전부) 모든 변수들을 추가해야 테스트 및 디버깅이 수월해진다고 한다.

 

// 잘못된 방법
function App() {
  // ...
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + varB), timeCount);
    return () => clearTimeout(timeout);
  }, []); // 피해야한다: varA 가 디펜던시 배열에 없다.
  return <span>Var A: {varA}</span>;
}

// 옳은 방법
function App() {
  // ...
  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + varB), timeCount);
    return () => clearTimeout(timeout);
  }, [varA, varB, timeCount]); // 이렇게 해야한다. 모든 디펜던시가 배열에 들어가 있다.
  return <span>Var A: {varA}</span>;
}

예제 코드를 약간 수정했다.

 

 


최근 리액트를 학습하며, 각종 훅을 머릿속에 끼얹고 있는 중이다.

리액트 공식 사이트에서 별도 페이지를 할애하면서 useEffect를 다루는 것을 보고, 그만큼 중요한 녀석이 아닐까 생각해서 그런지 재미읽게 읽은 느낌이다.

Comments