Frontend/React

React 성능 최적화: useMemo와 useCallback 완벽 가이드

sliver__ 2025. 7. 21. 18:18
728x90

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를 함께 사용하는 실전 예제를 통해 실제 코드에서 어떻게 작동하는지 알아보겠습니다.

728x90