Frontend/React

React 최적화: useMemo와 useCallback, 언제 쓰고 언제 말아야 할까?

미니임 2026. 2. 24. 15:32

 

React로 애플리케이션을 개발하다 보면 "성능 최적화"라는 숙제에 직면하게 됩니다. 이때 가장 먼저 떠올리는 것이 useMemo와 useCallback이죠. 하지만 이 도구들은 무조건 많이 쓴다고 성능이 좋아지는 '마법의 지팡이'가 아닙니다. 오히려 잘못 사용하면 메모리 낭비와 코드 복잡성만 높일 수 있습니다.

오늘은 이 두 Hook의 정확한 사용 시점과 주의사항을 풍부한 예제를 통해 알아보겠습니다.

1. useMemo: 계산된 값의 재사용

useMemo는 특정 연산의 결과값을 메모리에 저장(메모이제이션)해 두었다가, 의존성 배열에 있는 값이 변경될 때만 다시 계산하는 Hook입니다.

✅ 언제 써야 할까?

A. 복잡하고 비용이 큰 계산이 포함될 때

데이터가 수만 개인 배열을 필터링하거나, 복잡한 수학적 계산을 렌더링마다 반복해야 한다면 useMemo가 효과적입니다.

import React, { useMemo } from 'react';

function ExpensiveComponent({ data, filterQuery }) {
  // 데이터가 매우 클 경우, 렌더링마다 filter를 실행하는 것은 낭비입니다.
  const filteredData = useMemo(() => {
    console.log('복잡한 계산 수행 중...');
    return data.filter(item => item.name.includes(filterQuery));
  }, [data, filterQuery]); // data나 filterQuery가 바뀔 때만 다시 계산

  return (
    <ul>
      {filteredData.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

B. 참조 동일성(Referential Integrity) 유지가 필요할 때

자식 컴포넌트가 React.memo로 감싸져 있고, 부모가 객체를 prop으로 전달할 때 중요합니다. JavaScript에서 객체는 내용이 같아도 참조 주소가 다르면 "다른 것"으로 인식되기 때문입니다.

❌ 언제 쓰지 말아야 할까?

  • 단순한 연산: 변수 두 개를 더하거나, 간단한 문자열을 조합하는 정도는 useMemo를 사용하는 비용(메모리 할당 및 의존성 비교)이 계산 비용보다 더 클 수 있습니다.
  • 의존성 배열이 너무 자주 바뀌는 경우: 매 렌더링마다 의존성이 바뀌어 어차피 매번 계산된다면 최적화의 의미가 없습니다.

2. useCallback: 함수의 재사용

useCallback은 값 대신 함수 자체를 메모이제이션합니다. 컴포넌트가 리렌더링될 때마다 함수가 새로 생성되는 것을 방지합니다.

✅ 언제 써야 할까?

A. React.memo와 함께 사용하는 자식 컴포넌트의 props로 함수를 전달할 때

이것이 useCallback의 가장 흔하고 중요한 용도입니다.

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

// 자식 컴포넌트: React.memo로 불필요한 리렌더링 방지
const SearchButton = React.memo(({ onClick }) => {
  console.log('SearchButton 렌더링');
  return <button onClick={onClick}>검색하기</button>;
});

function ParentComponent() {
  const [text, setText] = useState('');

  // useCallback이 없다면 부모가 렌더링될 때마다 handleSearch가 새로 생성됨
  // 이로 인해 SearchButton도 (props가 바뀌었다고 판단하여) 리렌더링됨
  const handleSearch = useCallback(() => {
    console.log('검색 실행:', text);
  }, [text]); 

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <SearchButton onClick={handleSearch} />
    </div>
  );
}

B. useEffect 내의 의존성으로 함수가 들어갈 때

함수 내부에서 상태값을 참조하고 그 함수를 useEffect에서 호출한다면, 함수의 참조값이 변할 때마다 효과가 실행되는 무한 루프를 방지하기 위해 사용합니다.

❌ 언제 쓰지 말아야 할까?

  • 일반적인 이벤트 핸들러: React.memo를 쓰지 않는 일반 HTML 요소(button, input 등)에 전달하는 함수는 굳이 useCallback으로 감쌀 필요가 없습니다. 현대의 브라우저에서 함수 생성 자체는 매우 가벼운 작업입니다.

3. 최적화 도구 사용 전 체크리스트

최적화 코드를 작성하기 전에 스스로에게 다음 세 가지 질문을 던져보세요.

  1. "성능 저하가 실제로 체감되는가?"
    • Chrome DevTools의 Profiler 탭을 사용하여 실제로 렌더링 병목이 발생하는지 확인하세요.
  2. "컴포넌트 구조를 먼저 개선할 수는 없는가?"
    • 상태를 더 하위 컴포넌트로 내리거나(State Colocation), children prop을 활용해 컴포넌트 구성을 바꾸는 것만으로도 해결되는 경우가 많습니다.
  3. "메모이제이션 비용이 더 큰 것은 아닌가?"
    • 모든 Hook 사용은 메모리 소비와 의존성 비교 로직이라는 비용을 수반합니다.

요약

도구목적주요 사용처

useMemo 값(Value)의 재사용 복잡한 데이터 처리, 객체의 참조 동일성 유지
useCallback 함수(Function)의 재사용 React.memo 자식에게 전달하는 콜백 함수
React.memo 컴포넌트의 재사용 Props가 변하지 않으면 리렌더링 건너뜀

성능 최적화는 '예방'보다 '진단'이 우선입니다. 코드의 가독성을 유지하면서 꼭 필요한 곳에 전략적으로 useMemo와 useCallback을 적용하는 것이 진정한 숙련자의 태도일 것입니다.

반응형