Santos의 개발블로그

React useEffect: 개발자가 알아야 4가지 팁 본문

Language & Framework & Library/React

React useEffect: 개발자가 알아야 4가지 팁

Santos 2023. 11. 17. 17:33

* 이 글은 React useEffect: 4 Tips Every Developer Should know 번역하였습니다.

 

useEffect: 4 Tips Every Developer Should Know

Check out these 4 tips that will save your code. Use useEffect with React Hooks correctly by avoiding these common pitfalls.

medium.com

React hooks에 useEffect의 대하여 이야기해볼까요. 절대로 잊어버리면 안 될 4가지 팁을 공유드립니다. 

 

Use a useEffect for a SIngle purpose

React Hook을 사용할 때 복수의 useEffect 함수를 사용할 수 있습니다. 하지만 그렇게 좋은 것만은 아닙니다. 클린코드의 관점에서는 함수는 한가지 목적을 가지고 있어야 하기 때문입니다. 

 

useEffect를 단순한 목적의 함수로 분리한다면 의도하지 않은 useEffect함수의 실행을 예방할 수 있습니다. 물론 Dependency 배열도 사용해야 합니다. 

 

먼저 useEffect를 이용해 재귀적인 카운터를 구현하는 나쁜 코드를 작성해봅시다. 내부에서는 서로 관련이 없는 varA와 varB 변수를 사용합니다. 

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>
  );
}

한번의 상태 변경으로 varA와 varB 두 상태 변수에 업데이트가 발생하게 됩니다. 결론적으로 보이는 hook은 정상적으로 동작하지 않습니다. 

 

위에 예시는 짧은 코드이기 때문에 문제점을 찾을 수 있지만, 만약 더 길고 복잡한 코드였다면 이러한 문제를 찾기 힘들수도 있습니다. 

 

이러한 문제를 해결하기 위해서는 useEffect를 분리하는 것입니다. 아래와 같은 코드로 말이죠

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

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

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);
    return () => clearTimeout(timeout);
  }, [varB]);
  return (
    <span>
      Var A: {varA}, Var B: {varB}
    </span>
  );
}

Use custom hooks if possible

위에 예제를 다시 살펴보았을 때 만약 varA와 varB가 완전히 독립적이었으면 어땟을까요? 

 

Custom hook을 만든다면 두 state의 변수를 완전히 독립적으로 만들 수 있습니다. 또한 어떤 변수를 사용하는지 쉽게 파악할 수 있습니다. 

 

아래는 Custom hook으로 만든 코드입니다. 

function App() {
  const [varA, setVarA] = useVarA();
  const [varB, setVarB] = useVarB();

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

function useVarA() {
  const [varA, setVarA] = useState(0);

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);

    return () => clearTimeout(timeout);
  }, [varA]);

  return [varA, setVarA];
}

function useVarB() {
  const [varB, setVarB] = useState(0);

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);

    return () => clearTimeout(timeout);
  }, [varB]);

  return [varB, setVarB];
}

각각의 변수들은 고유의 hook을 가지게 되어 유지보수가 더 쉽고 이해하기가 더 쉬워졌습니다. 

The right way of conditional useEffect

setTimeout를 사용했던 예시에서 다음의 코드를 살펴봅시다. 

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

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);

    return () => clearTimeout(timeout);
  }, [varA]);

  return <span>Var A: {varA}</span>;
}

Counter를 5개까지 제한한다고 했을 때, 옳은 방법과 잘못된 방법을 살펴봅시다. 

잘못된 방법은 다음과 같습니다. 

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

  useEffect(() => {
    let timeout;
    if (varA < 5) {
      timeout = setTimeout(() => setVarA(varA + 1), 1000);
    }

    return () => clearTimeout(timeout);
  }, [varA]);

  return <span>Var A: {varA}</span>;
}

위에 코드는 정상적으로 동작하지만, setTimeout은 조건적으로 실행되는 반면 clearTimeout은 varA가 변경될 때마다 매번 실행이 될 것입니다. 

 

useEffect를 조건적으로 사용하는 좋은 방법은 함수 초기에 즉시 반환하는 것입니다. 아래의 코드에서 살펴보시죠. 

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

  useEffect(() => {
    if (varA >= 5) return;

    const timeout = setTimeout(() => setVarA(varA + 1), 1000);

    return () => clearTimeout(timeout);
  }, [varA]);

  return <span>Var A: {varA}</span>;
}

이 방법은 많은 곳에서 사용되고 있으며, 의도치 않은 useEffect의 실행을 막을 수 있습니다. 

Add all variables used in useEffect to the dependency array

ESLint를 사용한다면 exhaustive-deps의 경고를 본적이 있을 것입니다. 

 

이는 매우 중요합니다. application이 커질수록 useEffect에는 더 많은 dependency들이 추가될 수 있지만, 클로저가 제대로 작동하는 것을 만들기 위해서는 모든 dependency들을 dependency 배열에 추가해주어야 합니다. 

 

위에 작성하였던 setTimeout 예시를 재 사용해보죠. 여기서 setTimeout을 한 번만 실행하여 varA를 증가시킨다고 가정해 봅시다. 아래의 코드와 잘 못 짜여진 코드입니다. 

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

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);

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

  return <span>Var A: {varA}</span>;
}

위 코드는 동작할 것입니다. 그러나 만약 코드가 더 많아지거나 코드를 변경해야 할 때를 생각해보세요. 

 

이러한 상황에 대비하여 사용되는 모든 변수들은 dependency 배열에 모두 추가해야 합니다. 테스트를 쉽게 할 수 있고, 혹시 발생할 문제를 쉽게 탐지할 수 있게 만듭니다. 

 

잘 짜여진 코드는 다음과 같습니다. 

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

  useEffect(() => {
    if (varA > 0) return;

    const timeout = setTimeout(() => setVarA(varA + 1), 1000);

    return () => clearTimeout(timeout);
  }, [varA]); 

  return <span>Var A: {varA}</span>;
}

긴 글 읽어주셔서 감사합니다. 


< 참고자료 >

 

[사이트] #Midum

medium.com/swlh/useeffect-4-tips-every-developer-should-know-54b188b14d9c

 

useEffect: 4 Tips Every Developer Should Know

Check out these 4 tips that will save your code. Use useEffect with React Hooks correctly by avoiding these common pitfalls.

medium.com

<React> React useEffect: 4 Tips Every Developer Should Know end

Comments