React 성능 최적화: useMemo와 useCallback 완벽 가이드
React 개발을 하다 보면 "분명히 똑같은 데이터를 전달했는데 왜 이 컴포넌트는 계속 리렌더링될까?"라는 고민을 해보신 적이 있을 겁니다. 이 문제의 핵심은 객체와 함수의 참조(Reference) 때문입니다. 그리고 그 해결책은 바로 useMemo와 useCallback입니다.
이번 글에서는 이 두 훅(Hook)의 작동 원리와 언제 사용해야 하는지, 그리고 남용을 피하는 요령까지 정리해보겠습니다.
props가 변하지 않았는데 왜 컴포넌트는 리렌더될까?
먼저 React에서 일반적인 렌더링 과정을 다시 짚어볼게요.
- React 컴포넌트가 리렌더링되면, 함수 안의 모든 값, 객체, 함수가 새로 생성됩니다.
- 이때, 동일한 코드로 만든 함수나 객체라도 메모리 주소가 다르기 때문에 React는 이를 “다른 값”으로 인식합니다.
- 결과적으로, React.memo로 감싼 컴포넌트라도 props로 새로운 객체나 함수를 전달받으면 항상 리렌더링됩니다.
예시
const handleClick = () => console.log('clicked');
<ChildComponent onClick={handleClick} />
위 코드에서 handleClick은 렌더링마다 새로 생성되므로, ChildComponent는 매번 새로운 props를 받는 것으로 간주되어 React.memo가 무효화됩니다.
해결책: useMemo & useCallback으로 값 안정화하기
이 문제를 해결하려면 함수나 객체를 렌더링 간에 동일한 참조로 유지해야 합니다. React는 이를 위해 다음 두 훅을 제공합니다:
- useMemo: 값(객체, 배열 등)을 메모이제이션
- useCallback: 함수를 메모이제이션 (사실상 useMemo(fn, deps)의 특화 버전)
작동 방식: 의존성(dependencies)이 핵심
두 훅 모두 다음과 같은 구조로 작동합니다:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedFunction = useCallback(() => doSomething(x), [x]);
- 의존성 배열([]) 안의 값이 변하지 않으면 이전에 저장된 값을 재사용
- 의존성이 변할 경우 새로운 값으로 다시 계산
이 방식은 useEffect와 동일하게 작동하므로 익숙하게 사용할 수 있습니다.
✅ useMemo & useCallback이 필요한 3가지 상황
1. 객체/함수를 props로 전달할 때 (React.memo와 함께 사용)
const memoizedFn = useCallback(() => doSomething(), []);
<ChildComponent onClick={memoizedFn} />
→ onClick 함수가 항상 같게 유지되어 ChildComponent가 불필요하게 리렌더링되지 않음
2. 고비용 연산 결과를 캐싱할 때
const filteredList = useMemo(() => {
return hugeList.filter(item => item.active);
}, [hugeList]);
→ 리스트 필터링과 같은 무거운 계산을 캐시하여 리렌더링마다 반복하지 않도록 함
3. 다른 훅의 의존성 배열에서 무한 루프 방지
const memoizedObject = useMemo(() => ({ a: 1 }), []);
useEffect(() => {
console.log('Runs once');
}, [memoizedObject]); // 안정화된 참조로 인해 무한 반복 방지
안정화된 참조로 인해 무한 반복 방지 → 객체 참조가 매번 새로 만들어지지 않기 때문에 useEffect가 불필요하게 반복되지 않음
언제 쓰지 말아야 할까?
- 모든 함수나 객체에 일괄적으로 적용하면 오히려 코드 복잡도와 메모리 사용량만 증가합니다.
- 렌더링이 가볍고 빈도도 낮은 컴포넌트에는 필요 없습니다.
- 의존성 배열을 잘못 설정하면 캐싱이 무의미해지거나 버그를 유발할 수 있습니다.
요약: 필요한 곳에만 쓰는 것이 핵심입니다.
정리하며
useMemo와 useCallback은 React 렌더링 성능을 세밀하게 제어할 수 있는 강력한 도구입니다.
하지만 올바른 맥락에서 사용하지 않으면 오히려 복잡도만 증가할 수 있으니, 다음 기준을 기억하세요.
props로 객체/함수 전달 | 렌더링이 드문 경우 |
고비용 연산 | 계산이 거의 없는 경우 |
무한 useEffect 루프 방지 | 간단한 의존성인 경우 |
다음 글에서는 useMemo와 React.memo를 함께 사용하는 실전 예제를 통해 실제 코드에서 어떻게 작동하는지 알아보겠습니다.