React 컴포넌트에서 함수를 props로 자식 컴포넌트에 전달하는 경우, 불필요한 함수 재생성으로 인해 자식 컴포넌트가 매번 리렌더링되는 문제가 발생할 수 있다. 특히 React.memo와 함께 사용할 때 이 문제가 더욱 두드러진다. 이를 방지하고 성능을 개선하기 위해 useCallback을 활용한 최적화가 필요하다.
이번 글에서는 useCallback을 적용해 함수 재생성을 방지하고 렌더링 최적화를 이룬 사례를 소개한다.
아래와 같이 작성된 코드에서, onCreate, onUpdate, onDelete 함수는 컴포넌트가 렌더링될 때마다 새로 정의된다.
const onCreate = (content) => {
dispatch({
type: "CREATE",
data: { ... },
});
};
이러한 함수가 props로 전달되면, 자식 컴포넌트 입장에서는 매번 새로운 참조값을 받게 되어 React.memo의 비교 결과가 달라지고, 불필요한 리렌더링이 발생한다.
실제로 TodoItem과 같은 자식 컴포넌트에서 React.memo를 사용하더라도, 부모 컴포넌트에서 전달된 onUpdate, onDelete 함수가 매번 새로 생성되기 때문에 memo의 비교에서 항상 다르게 판단된다.
그래서 아래와 같이 함수 props를 비교하지 않도록 직접 비교 함수를 작성해줘야 했다:
export default memo(TodoItem, (prevProps, nextProps) => {
if (prevProps.id !== nextProps.id) return false;
if (prevProps.isDone !== nextProps.isDone) return false;
if (prevProps.content !== nextProps.content) return false;
if (prevProps.date !== nextProps.date) return false;
return true; // 함수 props(onUpdate, onDelete)는 비교하지 않음
});
이런 방식은 번거롭고, 비교 조건을 누락하면 리렌더링 이슈가 발생할 수 있다.
이 문제를 해결하기 위해 useCallback을 사용해 함수를 메모이제이션하면 다음과 같이 된다:
const onCreate = useCallback((content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
}, []);
같은 방식으로 onUpdate, onDelete도 최적화할 수 있다:
const onUpdate = useCallback((targetId) => {
dispatch({
type: "UPDATE",
targetId,
});
}, []);
const onDelete = useCallback((targetId) => {
dispatch({
type: "DELETE",
targetId,
});
}, []);
useCallback은 React 함수형 컴포넌트에서 함수 재사용성과 렌더링 최적화를 위한 핵심 훅이다. 특히 이벤트 핸들러나 액션 디스패처 함수를 자식에게 전달해야 할 때는 꼭 사용을 고려해보자. useCallback을 적절히 활용하면, 불필요한 비교 함수를 작성하지 않아도 되고, 컴포넌트 성능 개선에도 직접적인 효과가 있다.
React - useContext를 사용하기 전과 후의 구조 비교 (0) | 2025.06.08 |
---|---|
React - useMemo를 사용한 코드 vs 사용하지 않은 코드 비교 (0) | 2025.06.07 |
React - useState를 useReducer로 대체하는 간단 예제 (0) | 2025.06.05 |
React - props 전달 방식의 차이: `{...todo}` vs `todo={todo}` (0) | 2025.06.05 |
React - useEffect를 이용한 컴포넌트 라이프사이클 제어 정리 (1) | 2025.06.05 |