성능 최적화를 위해 사용되는 방법들은 다양합니다. 

React.memo는 리액트 컴포넌트의 불필요한 리렌더링을 방지하여 최적화를 이루는데 사용되고, 

useMemo는 복잡한 연산 결과를 메모이제이션하여 의존성안에 있는 값이 변하지 않았을 경우 다시 계산을 하지 않는데 사용됩니다.

 

useCallback은 함수의 참조를 유지함으로써 컴포넌트의 리렌더링에 마다 내부의 함수들이 재생성되는 것을 방지합니다.

말만 들었을 때는 React.memo든 useMemo든 useCallback이든 많이 사용해두면 좋을 것 같지만 그렇게 된다면 불필요한 메모이제이션으로 오버헤드가 커져 오히려 비효율성이 더욱 커질 수 있습니다.

 

그래서 아주 현명하게 최적화를 사용해야하는데, 오늘은 그 중에서 useCallback의 적절한 사용시기에 대해 알아보겠습니다.

 

useCallback 은 언제 사용하면 좋을까?

크게 2가지 경우를 생각하면 됩니다.

1. 자식 컴포넌트의 최적화

자식 요소를 React.memo를 통해 최적화를 해놓았는데, 부모요소로부터 자식 요소에 전달되는 함수가 존재한다면 사실 부모 요소가 리렌더링 될 때마다 해당 함수는 새롭게 정의될 것이고 이로 인해 함수가 변했다고 판단하여 자식 컴포넌트는 memo가 무색하게 항상 리렌더링이 발생합니다. 이럴 때는 useCallback이 필요합니다. 

const Parent = () => {
  const [count, setCount] = useState(0);

  // 함수 재생성을 방지
  const increment = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return <Child onClick={increment} />;
};

const Child = React.memo(({ onClick }) => {
  console.log("Child rendered!");
  return <button onClick={onClick}>Increment</button>;
});

 

 

2. useEffect 의존성 관리

만약 useEffect의 의존성 배열에 함수가 들어간다면, 해당 함수는 반드시 useCallback을 해주어야 합니다. 

(혹은, 애초에 해당 함수를 useCallback 안에서 정의하고 의존성 배열에서 빼버리는 방식도 있습니다.)

useCallback을 해두지 않는다면 useEffect는 의존성 배열안에 들어간 함수가 변했다고 인식하여 무한반복 호출되는 상황이 발생합니다.

const Example = () => {
  const [count, setCount] = useState(0);

  const logCount = useCallback(() => {
    console.log(`Count is ${count}`);
  }, [count]);

  useEffect(() => {
    logCount();
  }, [logCount]);
};

 

이렇게 2가지 이외에는,  저는 특별히 useCallback을 사용해야하는 이유가 없다고 생각합니다. 

단순히 하나의 컴포넌트에서 함수가 굉장히 크기가 크고 로직이 복잡할 때, 이 함수를 매번 재생성하는게 신경쓰여 useCallback을 사용할 수도 있지만 과연 useCallback을 통해 메모리와 cpu를 사용하는 것(오버헤드)을 방지하는 것이 아무것도 안하고 매번 새로운 정의, 참조를 만드는 것보다 효율적인지를 판단하기란 참 어려운 것 같습니다. (매번 성능을 측정해볼 수도 없고요!)

 

그래서 위의 필수적인 2가지 경우가 아니라면, 저는 useCallback을 굳이 고려하지는 않습니다.  

 

 

다만 계산의 과정이 1천번의 loop가 필요한 값의 경우(대략 1ms)에는 useMemo(값을 메모이제이션)를 사용하면 좋겠네요.

COMMENT