useMemo: 비싼 계산 결과를 캐싱하기

React 컴포넌트는 상태나 props가 바뀔 때마다 함수 전체가 다시 실행됩니다. 이때 계산 비용이 큰 작업이 있다면, 관련 없는 상태 변화에도 불필요하게 재계산이 일어납니다. useMemo는 이런 상황에서 계산 결과를 기억해두었다가 재사용합니다.

기본 사용법

import { useMemo } from 'react';
 
const cachedValue = useMemo(() => {
  return 계산하는_함수();
}, [의존성]);
  • 의존성 배열의 값이 바뀌지 않으면, 이전에 계산한 값을 그대로 반환합니다
  • 의존성 배열의 값이 바뀌면, 함수를 다시 실행하고 새 값을 저장합니다

실전 예제

function ProductList({ products, filterText }) {
  // filterText나 products가 바뀔 때만 다시 필터링
  const filteredProducts = useMemo(() => {
    console.log('필터링 실행됨');
    return products.filter(p =>
      p.name.includes(filterText)
    );
  }, [products, filterText]);
 
  return (
    <ul>
      {filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

filterTextproducts가 바뀌지 않은 채 다른 이유로 렌더링이 발생해도, 필터링 연산은 다시 실행되지 않습니다.

언제 사용해야 할까?

useMemo모든 계산에 쓰는 것이 좋은 게 아닙니다. 오히려 성능이 나빠질 수도 있습니다.

적합한 경우

  • 수천 개 데이터를 정렬/필터링하는 등 실제로 느린 연산
  • 결과를 다른 훅의 의존성으로 사용할 때 (객체/배열 참조 안정성)

적합하지 않은 경우

  • 단순한 덧셈, 문자열 연결 같은 가벼운 연산
  • 의존성 배열의 값이 거의 항상 바뀌는 경우
// 불필요한 useMemo: 단순 연산에는 그냥 계산하는 것이 더 빠름
const total = useMemo(() => price * quantity, [price, quantity]);
 
// 그냥 이렇게:
const total = price * quantity;

참조 안정성 (Reference Stability)

useMemo의 또 다른 중요한 용도는 객체나 배열의 참조를 안정적으로 유지하는 것입니다.

function Parent() {
  const [count, setCount] = useState(0);
 
  // count가 바뀌지 않으면 같은 객체 참조를 유지
  const config = useMemo(() => ({
    theme: 'dark',
    language: 'ko',
  }), []);
 
  return <Child config={config} />;
}

config 객체를 useMemo 없이 작성하면, Parent가 렌더링될 때마다 새 객체가 만들어집니다. 내용이 같아도 참조가 다르므로, Child가 불필요하게 리렌더링될 수 있습니다.

정리

항목내용
역할계산 결과를 캐싱하여 불필요한 재계산 방지
반환값계산된
사용 기준실제로 느린 연산, 또는 참조 안정성이 필요할 때
남용 주의가벼운 연산에 쓰면 오히려 오버헤드 발생

함수를 캐싱하고 싶다면 useMemo 대신 useCallback을 사용합니다.