본문으로 건너뛰기
yceffort
PostsSeriesTagsAbout🧪 Research
EN

Tweaks

theme
accent palette
film grain
minimal mode
BACK TO INDEX
◆ ESSAY
--min
--year
KOoriginal

mailMail icongithubtwitter
yceffort
•
© 2026
•
https://yceffort.kr
BACK TO INDEX
◆ ESSAY

리액트의 useCallback useMemo, 정확하게 사용하고 있을까

avatar
yceffort
2022-04-16 · 7분
7min
2022year
KOoriginal
react

Table of Contents

  • Introduction
  • 문제는 무엇일까
  • props의 참조 동일성은 언제 문제가 될까?
  • 지켜야할 규칙
    • useMemo와 useCallback을 사용하지 말아야할 경우
    • useMemo와 useCallback을 사용해야 하는 경우

Introduction

리액트 코드를 리뷰하다보면, useCallback과 useMemo를 정말 많은 곳에 사용하는 것을 발견하게 된다. 일반적으로 두 훅을 쓰게 되는 이유는 컴포넌트에 무언가 함수를 전달할 때 마다 useCallback을 사용하는 것 같지만, 이는 문제에 대한 올바른 해결방법이 아니며 오히려 렌더링 시간에 문제를 일으킬 수 있다.

많은 글에서 언급된 것처럼 useMemo와 useCallback을 이용한 최적화의 비용은 공짜가 아니다.

문제는 무엇일까

리액트 컴포넌트 트리는 매우 클 수 있다. React DevTools를 열고 애플리케이션을 살펴보면, 한번에 많은 컴포넌트가 렌더링 되는 것을 볼 수 있다. 이 과정에서 한두개 정도 불필요한 useCallback useMemo를 사용하는 것은 별 문제가 되지 않지만, 이 코드가 여기저기 존재한다면 문제가 될 수 있다.

일반적인 오해 중 하나로는, useCallback을 사용하면 렌더링중 함수 재생성을 방지할 수 있다는 것인데, 꼭 그런 것 만은 아니다.

useCallback은 제공된 deps를 기준으로 반환된 함수 객체를 메모이제이션 하는 것 뿐이다. 즉, 동일한 deps가 제공되면 (참조로 비교) 동일한 함수 객체를 반환한다.

만약 그냥 새로운 함수를 매번 만드는 대신 useCallback으로 선언된 함수를 컴포넌트나 훅으로 넘겨주는 경우, useCallback을 사용함으로써 새로운 함수를 만들고, 새로운 배열을 만들어서 함수를 실행하고, deps의 동일성을 비교하기 위한 함수와 종속성 집합을 메모리에 저장하게 될 것이다.

이는 단순히 함수를 props로 만들어서 전달하는 것보다 훨씬더 많은 비용이 든다. useCallback을 사용하든, 사용하지 않았든 기능적으로는 동일하게 동작했을 것이다.

props의 참조 동일성은 언제 문제가 될까?

만약 자식 컴포넌트가, React.memo를 사용하고 있거나 React.PureComponent로 구현되어 있는 경우, 리액트는 props가 정확하게 일치하는 한 부모 컴포넌트가 리렌더링 되더라도 이 자식 컴포넌트를 리렌더링하지 않을 것이다. 만약 다른 모든 props가 참조적으로 동일하지만, 만약 의도치 않게 새 함수 인트선트 또는 객체 인스턴스를 전달하게 되면, 해당 컴포넌트가 다시 리렌더링 된다.

이러한 컴포넌트에는 다시 렌더링하는데 비용이 많이 드는 하위 컴포넌트가 존재할 수 있으므로, memo에 대한 이러한 약속을 지키지 않으면 성능이 저하될 수 있다.

이러한 참조 동일성은 props가 useEffect의 종속성으로 사용되는 경우에도 문제가 될 수 있다. props가 변경될 때 마다 이 useEffect가 트리거 될 것이다.

지켜야할 규칙

useMemo와 useCallback을 사용하지 말아야할 경우

  1. host 컴포넌트에 (div span a img...) 전달하는 모든 항목에 대해 쓰지 말아야한다. 리액트는 여기에 함수 참조가 변경되었는지 신경쓰지 않는다. (ref, mergeRefs는 여기에서 제외된다.)
  2. leaf 컴포넌트에는 쓰지말아야 한다.
  3. useCallback useMemo의 의존성 배열에 완전히 새로운 객체와 배열을 전달해서는 안된다. 이는 항상 의존성이 같지 않다는 결과를 의미하며, 메모이제이션을 하는데 소용이 없다. useEffect useCallback useMemo의 모든 종속성은 참조 동일성을 확인한다.
// dont
const x = [‘hello’];
const cb = useCallback(()={},[prop1,prop2, x])

// dont
const [a, ...rest] = someArray;
const cb = useCallback(()={},[rest]
  1. 전달하려는 항목이 새로운 참조여도 상관없다면, 사용하지 말아야 한다. 매번 새로운 참조여도 상관없는데, 새로운 참조라면 메모이제이션하는 것이 의미가 없다.

host 컴포넌트: 호스트 환경 (브라우저 또는 모바일)에 속하는 플랫폼 컴포넌트를 의미한다. DOM 호스트의 경우, div, img와 같은 요소가 될 수 있다. leaf 컴포넌트: DOM에서 다른 컴포넌트를 렌더링하지 않는 컴포넌트 (html 태그만 렌더링하는 컴포넌트)

useMemo와 useCallback을 사용해야 하는 경우

  1. 하위트리에 많은 Consumer가 있는 값을 Context Provider에 전달해야 하는 경우 useMemo를 사용하는 것이 좋다. <ProductContext.Provider value={{id, name}} >의 경우, 어떤 이유로든 해당 컴포넌트가 리렌더링 된다면 id name이 동일하더라도 매번 새로운 참조를 만들어 죄다 리렌더링 될 것이다.
  2. 계산 비용이 많이 들고, 사용자의 입력 값이 map과 filter을 사용했을 때와 같이 이후 렌더링 이후로도 참조적으로 동일할 가능성이 높은 경우, useMemo를 사용하는 것이 좋다.
  3. ref 함수를 부수작용과 함께 전달하거나, mergeRef-style 과 같이 wrapper 함수 ref를 만들 때 useMemo를 쓰자. ref 함수가 변경이 있을 때마다, 리액트는 과거 값을 null로 호출하고 새로운 함수를 호출한다. 이 경우 ref 함수의 이벤트 리스너를 붙이거나 제거하는 등의 불필요한 작업이 일어날 수 있다. 예를 들어, useIntersectionObserver가 반환하는 ref의 경우 ref 콜백 내부에서 observer의 연결이 끊기거나 연결되는 등의 동작이 일어날 수 있다.
  4. 자식 컴포넌트에서 useEffect가 반복적으로 트리거되는 것을 막고 싶을 때 사용하자.
  5. 매우 큰 리액트 트리 구조 내에서, 부모가 리렌더링 되었을 때 이에 다른 렌더링 전파를 막고 싶을 때 사용하자. 자식 컴포넌트가 React.memo React.PureComponent일 경우, 메모이제이션된 props를 사용하게되면 딱 필요한 부분만 리렌더링 될 것이다.

React DevTools Profiler를 사용하면 컴포넌트의 리렌더링 속도가 느린 경우, 상태 변경이 일어났을 때 얼마나 렌더링 시간이 걸렸는지 조사할 수 있다. 이렇게 하면 거대한 계단식 리렌더링을 방지하기 위해 React.memo를 사용할 위치를 찾을 수 있고, 필요한 경우 useCallback useMemo를 사용하여 상태변경을 더 효율적으로 만들 수 있다.

관련 글

  • #ai#essay#frontend

    프론트엔드는 어디서 왔고, 에이전트 이후 어디로 가는가

    층은 왜 쌓였고, 왜 서버로 돌아왔고, 에이전트 이후에도 스택이 남는 이유는 무엇인가. 그리고 스택이 이기는 것과 그 스택을 아는 사람의 가치는 왜 별개인가

    2026-07-22·34분
  • #react#react-server-components#memoization

    React cache() 딥다이브: 소스 코드로 읽는 요청 단위 메모이제이션

    React cache() 함수의 모든 이상한 규칙은 30여 줄짜리 구현에서 직접 따라 나온다. dispatcher, getCacheForType, WeakMap/Map 트리를 소스 레벨로 따라가며 요청 단위 메모이제이션의 동작을 끝까지 본다.

    2026-05-30·35분
  • ◆ 디렉티브 딥다이브
    #react#nextjs#frontend

    'use cache' 디렉티브 딥다이브: 캐시 경계의 끝까지

    "use cache" 한 줄이 만드는 빌드 타임 변환, 캐시 키 직렬화, ResumeDataCache, cacheHandler, 그리고 Cache Components까지

    2026-05-01·65분
  • ◆ 디렉티브 딥다이브
    #react#nextjs#frontend

    'use client' 디렉티브 딥다이브: 클라이언트 경계의 끝까지

    "use client" 한 줄이 만드는 모듈 경계, 빌드 타임 변환, Flight 직렬화, 그리고 성능까지

    2026-05-01·52분

새 글을 놓치고 싶지 않으시다면 RSS로 구독해 주세요.

RSS 구독 →

yceffort — 프론트엔드 엔지니어입니다. 발표·기술 자문·기고 문의는 이곳에서 받고 있습니다.

← Back to the blogIssue on GitHub →