우당탕탕

React useMemo와 useCallback, 진짜 필요할 때만 사용하는 체크리스트 본문

언어/JavaScript

React useMemo와 useCallback, 진짜 필요할 때만 사용하는 체크리스트

모찌모찝 2026. 6. 23. 18:08

React 컴포넌트 최적화를 하다가 useMemouseCallback을 무조건 써야 한다고 생각했는데, 실제로는 오히려 성능이 더 나빠지는 경우가 많아 고민이 많았어요. 이걸 구현하면서 겪은 삽질과 그 과정에서 꼭 확인해야 할 체크리스트를 정리해 봤습니다.

이 글에서는 React Hooks로 최적화를 할 때 정말 필요한 순간과, 잘못 사용해 오히려 복잡해지거나 성능이 떨어지는 경우를 구분할 수 있도록 도와드릴게요. 그리고 놓치기 쉬운 체크리스트도 공유하니 꼭 끝까지 읽어보세요.

개발 환경 / 버전 정보

제가 테스트한 환경은 React 18.2, TypeScript 4.9 조합이에요. 물론 JavaScript 환경에서도 동일한 개념이 적용됩니다.

React useMemo와 useCallback, 이렇게 쓰면 됩니다

사실 이 부분이 많은 분들이 헷갈리는 지점인데요, useMemo와 useCallback은 재계산과 함수 재생성 비용이 큰 작업에만 사용하는 게 맞습니다. 단순한 변수 계산이나 인라인 함수 작성에 무조건 쓰면 오히려 React가 관리해야 할 캐시가 늘어나서 성능이 떨어지더라고요.

예를 들어, 배열 필터링 같이 계산 비용이 높은 작업에 useMemo를 쓰는 예시를 보여드릴게요.

import React, { useState, useMemo } from 'react';

function FilteredList({ items }) {
  const [filter, setFilter] = useState('');

  // 비용이 큰 필터링 작업을 useMemo로 감싸 재계산 방지
  const filteredItems = useMemo(() => {
    console.log('필터링 연산 실행');
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  return (
    <div>
      <input type="text" value={filter} onChange={e => setFilter(e.target.value)} placeholder="검색어 입력" />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

위 코드를 보면 filter가 바뀔 때만 필터링 작업이 재실행되니 불필요한 렌더링 비용이 줄어드는 걸 알 수 있어요.

반면, 단순한 문자열 조합 같은 가벼운 계산에 useMemo를 쓰면 오히려 메모리 관리 오버헤드가 더 클 수 있습니다.

useCallback은 주로 자식 컴포넌트에 넘길 핸들러 함수를 메모이제이션할 때 쓰는데, 역시 꼭 필요한 경우에만 사용하는 걸 추천해요.

import React, { useState, useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  // count가 변할 때만 함수 새로 생성
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  return <Child onClick={increment} />;
}

function Child({ onClick }) {
  console.log('Child 렌더링');
  return <button onClick={onClick}>증가</button>;
}

이렇게 하면 increment 함수가 불필요하게 재생성되는 걸 막아서 Child 컴포넌트의 불필요한 렌더링을 줄일 수 있어요.

꼭 체크해야 하는 useMemo/useCallback 사용 체크리스트 ✅

  • 연산 비용 확인: useMemo는 재계산 비용이 큰 함수에만 쓰세요. 단순 연산에는 오히려 부담입니다.
  • 의존성 배열 누락 주의: 의존성 배열을 빼먹으면 캐시가 잘못되어 버그가 생길 수 있으니 꼭 확인하세요.
  • 함수 재생성 비용 고려: useCallback은 자식 컴포넌트에 넘겨 자주 재렌더링되는 핸들러에만 적용하세요.
  • 불필요한 메모이제이션 지양: 지나친 사용은 코드 복잡도만 높이고 성능에 도움 안 됩니다.
  • React.memo와 함께 활용: useCallback과 React.memo는 시너지가 있어요. 자식 컴포넌트를 메모이제이션할 때 동시에 쓰면 효과적입니다.

여기서 삽질했던 부분들

저도 처음엔 모든 함수와 복잡한 계산에는 무조건 useMemouseCallback을 붙였는데, 의존성 배열을 빼먹어서 버그가 한참 났어요. 예를 들면 이런 에러 메시지가 뜨더라고요.

Warning: React Hook useMemo has a missing dependency: 'someValue'.
Either include it or remove the dependency array.

이걸 무시하고 사용하면 값이 바뀌어도 state가 갱신되지 않아 UI가 꼬였어요. 그래서 의존성 배열을 꼼꼼히 채우는 습관이 생겼습니다.

또, useCallback을 쓰면서 자식 컴포넌트에 함수가 제대로 전달되는지 확인 안 해서 렌더링이 안 되는 문제도 있었는데, React.memo와 함께 쓰면서 이 문제를 해결했습니다.

심화: 이것도 알면 좋아요

useMemo와 useCallback을 사용할 때 메모리 누수 가능성도 생각해보셔야 해요. 너무 많은 메모이제이션은 캐시 메모리를 쓸데없이 차지할 수 있거든요.

또한, React 18에서는 자동 배치 기능 때문에 렌더링 방식이 약간 바뀌었는데, 이 때문에 불필요한 메모이제이션이 오히려 성능 저하를 낼 수도 있으니 렌더링 성능을 실제 측정하면서 최적화하세요.

마지막으로, useMemo는 값이 아니라 참조 자체를 캐싱하는 거라, 객체나 배열을 반환할 때도 꼭 의존성 배열을 신경 써서 넣어야 합니다.

자주 물어보시는 것들

Q. 그냥 모든 함수에 useCallback을 쓰면 안 되나요?

A. 자주 재생성되는 함수가 아니라면 오히려 코드가 복잡해지고 성능도 나빠질 수 있어요. 자식 컴포넌트가 리렌더링 되는 걸 막기 위한 목적이라면 React.memo와 같이 사용할 때 효과적입니다.

Q. useMemo가 없으면 어떻게 되나요?

A. 매 렌더링마다 해당 함수가 실행돼서 비용이 많이 듭니다. 간단한 계산이면 괜찮지만 데이터가 크거나 복잡한 작업이라면 화면이 버벅일 수 있습니다.

Q. 의존성 배열에 객체를 넣어도 되나요?

A. 객체는 참조가 계속 바뀌기 때문에, 객체 자체를 넣으면 매번 캐시가 무용지물이 될 수 있어요. 대신 객체의 특정 프로퍼티를 의존성으로 넣거나, 객체를 안정적으로 관리하는 방법을 써야 합니다.

useMemo와 useCallback을 정말 필요한 순간에만 써서 React 앱 성능과 코드 유지 보수성을 동시에 챙길 수 있어요. 다음에는 React.memo와 함께 쓰는 실제 사례도 정리해볼 예정인데, 이번 글에서 공유한 체크리스트 꼭 기억해 주세요.

Comments