React에서 useCallback은 컴포넌트가 리렌더링될 때 동일한 함수가 새롭게 생성되는 문제를 줄여주는 훅입니다. 하지만 모든 함수에 useCallback을 적용할 필요는 없습니다. 이번 글에서는 useCallback을 언제 사용해야 하고, 언제 불필요한지에 대해 정리해보겠습니다.
useEffect의 의존성이 아닐 때useEffect 내부에서 특정 함수를 사용하지 않는다면, 해당 함수가 매 렌더링마다 새로 생성되더라도 큰 문제가 되지 않습니다. 따라서 useCallback을 사용할 필요가 없습니다.
부모 컴포넌트가 자식 컴포넌트에게 함수를 props로 넘길 경우, 부모가 리렌더링될 때마다 새로운 함수가 생성됩니다. 이때 React.memo를 사용한 자식 컴포넌트가 불필요하게 리렌더링될 수 있습니다. 하지만 props로 전달되지 않는 함수라면 useCallback이 필요 없습니다.
함수가 간단한 연산을 수행하거나 호출 빈도가 낮다면, 매번 새로운 함수가 생성되더라도 성능에 큰 영향을 미치지 않습니다. 오히려 useCallback을 남용하면 코드의 복잡도만 증가할 수 있습니다.
setState 함수에 사용할 필요가 없음React에서 setState 함수(setAuthVisited, setSignupForm 등)는 내부적으로 동일한 함수 참조를 유지하므로, useCallback을 사용하지 않아도 됩니다. 따라서 setState를 useCallback으로 감싸는 것은 불필요한 최적화입니다.
useEffect(() => {
updateData();
}, [updateData]); // updateData가 매번 변경되면 useEffect가 불필요하게 실행됨
위와 같은 경우, updateData가 매 렌더링마다 새로 생성되면 useEffect가 불필요하게 실행됩니다. 이를 방지하려면 useCallback으로 감싸야 합니다.
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return <Child onClick={handleClick} />;
};
const Child = React.memo(({ onClick }) => {
console.log("Child 렌더링");
return <button onClick={onClick}>Click</button>;
});
위 코드에서 handleClick을 useCallback 없이 선언하면, Parent가 리렌더링될 때마다 새로운 함수가 생성되어 Child도 불필요하게 리렌더링됩니다. useCallback을 사용하면 이를 방지할 수 있습니다.
useCallback을 사용할지 말지는 다음 기준을 고려하면 됩니다.
| 상황 | useCallback 필요 여부 |
|---|---|
useEffect 의존성 배열에 포함됨 | ✅ 필요 |
함수가 props로 전달됨 | ✅ 필요 |
상태 변경 함수 (setState) | ❌ 불필요 (React가 보장) |
| 내부에서만 사용하는 함수 | ❌ 불필요 |
| 함수가 간단하거나 호출 빈도가 낮음 | ❌ 불필요 |
"함수가 useEffect의 의존성이 아니고, props로 전달되지 않으며, 성능 최적화가 필요하지 않다면 useCallback은 불필요하다."
앞으로 useCallback을 사용할 때 이 기준을 참고하면 불필요한 최적화를 피하고 코드의 가독성을 높일 수 있을 것입니다. 🚀