TIL: React의 의존성 관리

재밌는 질문을 받았다. 그리고 답변을 고민하다가 React가 의존성을 관리하는 방식에 대해 알게 되어서 글로 남긴다.

질문

질문은 아래 컴포넌트를 렌더링하고 버튼을 2번째 눌렀을 때 왜 저 함수가 다시 호출되면서 console.log가 실행되는지 모르겠다는 내용이었다.

import { useEffect, useState } from "react";

function UseEffectTest() {
  // state only for rerender
  const [foo, setFoo] = useState(true);

  console.log("outside of useEffect, but inside of component", "foo: ", foo);

  useEffect(() => {
    console.log("RE-RENDERED", "foo: ", foo);
  });

  return (
    <div>
      {console.log("---inside JSX---", "foo: ", foo)}
      <button
        onClick={() => {
          setFoo(false);
        }}
      >
        rerender me!
      </button>
    </div>
  );
}

export default UseEffectTest;

나도 모르던 내용이라 처음엔

스트릭트 모드 때문 아닌가?

했다. 그런데 프로덕션 빌드에서도, next@12react@17로 테스트해봐도 똑같더라. 그리고 버튼을 더 눌러도 다시 렌더링이 되지 않았다.

고민하다가 깨달았다. 이건 React API의 한계다.

답변

React 함수 컴포넌트는 의존성 배열이라는 걸 사용한다. 그리고 fooboolean 타입을 가진 변수인데, 의존성 배열에 들어갈 수 있다. 변화 내용을 추적하거나 의존성 그래프를 그리기 위한 정보가 아예 없는, boolean 같은 값들이 의존성 배열에 직접 들어간다는 것이다. use*는 의존성 그래프 없이 다시 실행할지 여부를 결정해야한다. 이럴 때 쓸 수 있는 방법이 foofalse로 놓고 컴포넌트를 다시 실행해보는 것이다. 실행하면서 의존성 배열에 다른 값이 들어오는지 확인하면, foofalse로 바뀌었을 때 다시 실행돼야하는 코드가 있는지 알 수 있다. 이후에 버튼을 다시 클릭해도 로그가 찍히지 않는 건 이전 실행의 결과로 foofalse로 바뀌어도 다시 실행될 필요가 없다는 걸 알았기 때문일 것이다.


오랜만에 답변하면서도 배울 게 있는 질문이었다.