[React] 30. useCallback과 함수 재생성 방지

서회정's avatar
Mar 07, 2026
[React] 30. useCallback과 함수 재생성 방지

 
memo 메서드 예제에서 만들었던 코드는 props
하나하나 비교하여 변경되었는지 여부를 판단해야했다.
 
유지보수 측면에서도 좋지 않고 코드가 길어지기 때문에
네 가지 props의 상태 변화를 비교하는 것보다
onUpdate, onDelete가 애초에 재생성되지 않게
방지하는 것이 더 좋은 방법이 될 수 있다.
 
export default memo(TodoItem, (prevProps, nextProps) => { if (prevProps.id !== nextProps.id) return false; if (prevProps.isComplete !== nextProps.isComplete) return false; if (prevProps.content !== nextProps.content) return false; if (prevProps.date !== nextProps.date) return false; return true; });
 

1. useCallback 사용

 
TodoItemonUpdate, onDelete 함수를 props로 전달하던
App컴포넌트에서 재생성을 막는 useCallback 훅을 사용해보자.
 

1. useCallback의 문법

 
먼저 App컴포넌트에서 useCallback 훅을 불러와서 호출해주자.
그리고 첫 번째 인수로는 콜백 함수로 최적화하고싶은 함수를 넣고
두 번째 인수로는 deps를 넣어주자.
 
useCallback(() => {}, []);
 
기본적으로 useCallback은 첫 번째 인수로 들어가는
콜백 함수의 결과값을 그대로 반환한다.
 
따라서 이를 변수에 바로 넘겨줄 수 있는데, deps의 값이
변경 될 때에만 다시 생성하게 된다.
 
const func = useCallback(() => {}, []);
 
그래서 deps를 빈 배열로 두게 되면 이 컴포넌트가
마운트 될 때에만 함수를 생성하게 된다.
 

2. useCallback 적용

 
그럼 useCallback으로 메모이제이션하고싶은
함수를 변경해보자.
 
onUpdate, onDelete 뿐만 아니라 onCreate 함수도
같이 useCallback메서드를 활용하여 최적화하자.
 
const onCreate = (content) => { dispatch({ type: "CREATE", data: { id: idRef.current++, isComplete: false, content: content, date: new Date().getTime(), }, }); }; const onUpdate = (targetId) => { dispatch({ type: "UPDATE", targetId: targetId, }); }; const onDelete = (targetId) => { dispatch({ type: "DELETE", targetId: targetId, }); };
 
기존에 있던 함수의 콜백함수 전체 부분을 useCallback
첫 번째 인수로 넣어주고, 빈 배열의 deps를 넣으면 된다.
 
따라서 다음과 같이 변경할 수 있다.
 
const onCreate = useCallback((content) => { dispatch({ type: "CREATE", data: { id: idRef.current++, isComplete: false, content: content, date: new Date().getTime(), }, }); }, []); const onUpdate = useCallback((targetId) => { dispatch({ type: "UPDATE", targetId: targetId, }); }, []); const onDelete = useCallback((targetId) => { dispatch({ type: "DELETE", targetId: targetId, }); }, []);
 
더 이상 해당 함수들이 재생성되지 않기 때문에 TodoItem
컴포넌트에서 커스텀 최적화 코드를 사용하지 않아도 되니
수정해주자.
 
export default memo(TodoItem);
 
복잡한 코드 없이도 최적화되어 불필요한 리렌더링 방지가 된다.
 
notion image
 

2. 최적화 작업 주의사항

 
리액트에서 최적화는 무조건 빨리 한다고 좋은 것이 아니다.
필요하지 않은 최적화를 너무 일찍 적용하면 코드가 복잡해지고,
오히려 유지보수와 기능 추가가 더 어려워질 수 있다.
 
즉, 최적화는 “미리 하는 작업”이 아니라, 필요한 지점을 확인한 뒤 진행하는 작업이라고 보는 것이 좋다.
 

1. 최적화는 언제 하는 게 좋을까?

 
보통 최적화는 기능 구현이 어느 정도 끝난 뒤 진행하는 것이 좋다.
초반부터 최적화를 너무 많이 적용하면,
 
  • 코드가 불필요하게 복잡해지고
  • 기능 수정이 어려워지며
  • 최적화 로직이 오히려 깨질 수 있다
 
특히 useCallback, useMemo, React.memo 같은 도구는
성능 개선을 위해 사용하는 것이지만,
잘못 사용하면 코드만 복잡해지고 실제 성능 차이는 거의 없을 수도 있다.
 
따라서 먼저 기능이 올바르게 동작하도록 구현하고,
그다음 불필요한 리렌더링이나 무거운 연산이 실제로
발생하는 부분을 확인한 뒤 최적화하는 것이 바람직하다.
 

정리

최적화는 기능 개발 전에 미리 적용하는 것이 아니라,
기능 구현 후 성능상 문제가 보이는 부분에 선택적으로 적용하는 것이 좋다.
 

 

2. 어떤 것들을 최적화하면 좋을까?

 
모든 것을 다 최적화할 필요는 없다.
성능에 실제로 영향을 주는 부분을 중심으로 최적화해야 한다.
대표적으로 아래와 같은 대상들을 우선적으로 살펴볼 수 있다.
 

1) 불필요하게 자주 리렌더링되는 컴포넌트

 
부모 컴포넌트가 렌더링될 때마다
자식 컴포넌트도 계속 다시 렌더링된다면 비효율이 생길 수 있다.
 
특히 자식 컴포넌트가 크거나 개수가 많다면
이런 리렌더링 비용이 커질 수 있으므로 최적화 대상이 될 수 있다.
 
예시:
  • 리스트 아이템이 많은 컴포넌트
  • 화면에 그려지는 UI가 복잡한 컴포넌트
  • 동일한 props를 받는데도 계속 다시 렌더링되는 컴포넌트
 
이럴 때 React.memouseCallback을 함께 고려할 수 있다.
 

 

2) 계산 비용이 큰 연산

 
렌더링될 때마다 무거운 계산이 반복되면 성능이 떨어질 수 있다.
 
예시:
  • 정렬, 필터링, 검색
  • 대량 데이터 가공
  • 복잡한 수식 계산
  • 매번 새로 만드는 큰 배열/객체
 
이런 경우에는 useMemo를 사용해서
필요할 때만 다시 계산하도록 최적화할 수 있다.
 

 

3) 자식에게 전달하는 함수

 
부모 컴포넌트가 렌더링될 때마다 함수가 새로 생성되면
그 함수를 props로 받는 자식 컴포넌트도 다시 렌더링될 수 있다.
 
특히 자식 컴포넌트가 React.memo로 감싸져 있다면,
함수 참조값이 계속 바뀌는 문제를 막기 위해 useCallback을 사용할 수 있다.
 
즉, useCallback함수를 재사용해서 자식 컴포넌트의
불필요한 리렌더링을 줄이고 싶을 때 의미가 있다.
 

 

3. 무조건 최적화하면 안 되는 이유

 
최적화도 비용이 든다.
 
예를 들어 useCallback은 함수를
기억해두는 대신 의존성 배열을 관리해야 하고,
코드를 읽는 입장에서는 흐름이 더 복잡해질 수 있다.
 
그래서 간단한 컴포넌트나 리렌더링 비용이
거의 없는 부분까지 전부 최적화하면
 
  • 코드 가독성이 떨어지고
  • 유지보수가 어려워지며
  • 실제 성능 향상은 거의 없을 수 있다
 
즉, 최적화는 필요한 곳에만 적용해야 한다.
 

 

4. 최적화할 때 기준

 
최적화를 고려할 때는 아래 기준으로 판단하면 좋다.
 
  • 리렌더링이 너무 자주 발생하는가?
  • 렌더링할 때 무거운 계산이 반복되는가?
  • 리스트가 많거나 UI 구조가 복잡한가?
  • props가 바뀌지 않았는데도 자식이 계속 다시 렌더링되는가?
  • 실제로 화면이 느리거나 버벅이는가?
 
이런 문제가 보일 때 최적화를 적용하면 된다.
 

 

한 줄 정리

 
리액트 최적화는 처음부터 무조건 하는 것이 아니라,
기능 구현 후 성능 문제가 보이는 컴포넌트, 연산, 함수에만 선택적으로 적용하는 것이 좋다.
 
 
Share article

clubnerdy