Files
2nd/00_Raw/Memoization.md
T

9.6 KiB

Memoization

📌 Brief Summary

Memoization(메모이제이션)은 입력값이 변경되지 않았을 때 계산 결과나 컴포넌트의 렌더링 결과를 캐싱하여 재사용하는 성능 최적화 기법이다 [1]. React에서는 React.memo, useMemo, useCallback과 같은 API를 통해 수동으로 제어할 수 있으며, 불필요한 리렌더링을 방지하여 애플리케이션의 반응성을 향상시킨다 [1, 2]. 최근에는 React Compiler를 통해 빌드 타임에 자동으로 더 세밀한 단위의 메모이제이션을 삽입하여 수동 관리의 복잡성을 줄이는 방식도 도입되었다 [2-4].

📖 Core Content

  • 수동 메모이제이션 기법: 개발자는 컴포넌트 전체의 렌더링 결과를 캐싱하기 위해 React.memo를, 계산 비용이 높은 파생 데이터(derived data)를 캐싱하기 위해 useMemo를, 함수 참조를 캐싱하기 위해 useCallback을 사용한다 [1, 2]. React.memo()는 고차 컴포넌트(HOC)로 작동하며, 전달받는 prop이 변경되지 않으면 렌더링을 건너뛰고 마지막 결과를 재사용한다 [5, 6].
  • 참조 안정성(Reference Stability)과 얕은 비교(Shallow Comparison): React는 리렌더링 여부를 결정할 때 prop에 대해 얕은 비교를 수행한다 [7]. 내용이 동일하더라도 매 렌더링마다 새롭게 생성되는 객체나 인라인 함수는 새로운 참조로 인식되어 메모이제이션을 무력화한다 [7-9]. 따라서 useMemouseCallback은 이러한 값과 함수의 참조를 안정적으로 유지하여 하위 컴포넌트의 불필요한 렌더링을 방지하는 데 핵심적인 역할을 한다 [7, 10].
  • 자동화된 메모이제이션 (React Compiler): React Compiler는 빌드 시 코드를 분석하고 자동으로 메모이제이션 로직을 삽입하는 최신 도구이다 [2, 3]. 컴포넌트 전체를 래핑하는 기존 수동 방식과 달리, 개별 JSX 요소와 연산을 독립적으로 캐싱하는 더 세밀한 수준(granular level)의 최적화를 수행한다 [4, 11]. 이를 통해 개발자는 메모이제이션 코드로 인한 혼란 없이 직관적이고 깔끔한 React 코드를 작성할 수 있다 [3, 11].
  • 적용 기준: 메모이제이션은 순수 컴포넌트이거나 빈번하고 피할 수 없는 리렌더링이 발생하는 경우, 복잡한 DOM을 렌더링하거나 값비싼 연산(정렬, 필터링 등)을 수행할 때 적용해야 한다 [6, 12]. 측정 없이 무분별하게 적용하는 것은 지양해야 하며, React Profiler 등을 통해 실질적인 성능 저하가 증명되었을 때만 사용해야 한다 [12, 13].

⚖️ Trade-offs & Caveats

  • 비교 연산 오버헤드: 메모이제이션은 공짜가 아니다. React는 이전 prop을 저장하고, 새로운 prop과 비교하며, 업데이트 여부를 결정하는 오버헤드를 부담해야 한다 [14]. 렌더링 자체가 빠르고 prop이 자주 변경되는 컴포넌트의 경우, 메모이제이션을 위한 비교 단계가 실제 렌더링보다 더 많은 컴퓨팅 자원을 소모할 수 있다 [12, 14].
  • 수동 관리의 한계: 수동 메모이제이션은 코드를 복잡하게 만들고, 종속성 배열을 잘못 관리하거나 객체 참조를 실수로 갱신하여 메모이제이션 체인을 깨뜨리는 등의 휴먼 에러를 유발하기 쉽다 [2, 15].
  • React Compiler의 제약 사항:
    • React 규칙의 엄격한 준수: 컴파일러가 정상적으로 최적화를 수행하려면 불변성 유지, 렌더링 중 부작용(Side effects) 방지 등 "Rules of React"를 엄격히 따라야 한다 [16, 17].
    • 서드파티 라이브러리 호환성: 매 렌더링마다 새로운 객체 참조를 반환하는 커스텀 훅(예: TanStack Query의 useMutation, React Router의 useLocation 등)은 컴파일러의 메모이제이션 체인을 단절시킨다 [18, 19]. 이 경우 결국 수동 메모이제이션이 필요할 수 있다 [19, 20].
    • 디버깅 가시성 저하: 컴파일러는 블랙박스처럼 동작하기 때문에, 컴포넌트가 예상치 않게 리렌더링될 때 최적화 실패 원인을 코드 상에서 즉각적으로 파악하기 어렵다 [21]. 또한 React 개발자 도구의 'Memo ' 배지는 컴파일러가 코드를 처리했음을 의미할 뿐, 런타임에 실제로 최적화가 성공하여 리렌더링이 방지되었음을 보장하지 않아 혼동을 줄 수 있다 [18].

🔗 Knowledge Connections

[아키텍처/기반 기술]

  • React Compiler

    • 연결 이유: 수동 메모이제이션의 복잡성을 줄이고 빌드 타임에 자동으로 세밀한 메모이제이션 로직을 삽입하여 성능을 최적화하는 도구이다 [3, 4].
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 개발자가 수동으로 작성하던 useMemouseCallback이 빌드 프로세스 내에서 어떻게 추상화되고 최적화되는지 파악할 수 있다 [4, 11, 20].
  • Shallow Comparison (얕은 비교)

    • 연결 이유: React.memo가 리렌더링 여부를 판단할 때 기본적으로 사용하는 평가 방식이다 [6, 7].
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 내용이 같은 객체나 인라인 함수가 왜 새로운 prop으로 취급되어 메모이제이션을 실패하게 만드는지 그 원리를 이해할 수 있다 [7-9].

[구현/활용 도구]

  • React Profiler & why-did-you-render
    • 연결 이유: 컴포넌트의 렌더링 횟수, 소요 시간, 그리고 불필요한 렌더링 트리거를 식별하여 메모이제이션이 필요한 지점을 찾아내는 진단 도구들이다 [22-25].
    • 이 개념을 통해 더 깊게 이해할 수 있는 부분: 맹목적인 최적화 대신 객관적 측정 데이터에 기반한 전략적 메모이제이션 적용 방법을 배울 수 있다 [13, 26].

Deeper Research Questions

  • React Compiler의 자동화된 캐싱 메커니즘은 기존의 컴포넌트 레벨 메모이제이션(React.memo)과 비교할 때, 어떻게 개별 JSX 요소 수준의 세밀함(granularity)을 달성하는가?
  • React.memo에 사용자 정의 비교 함수(Custom Comparison Function)를 적용하는 깊은 비교 방식이 얕은 비교에 따른 렌더링 비용 절감보다 성능상 불리해지는 임계점은 어디인가?
  • TanStack Query나 Material UI와 같이 불안정한(Unstable) 객체 참조를 반복적으로 반환하는 라이브러리를 사용할 때, React Compiler의 한계를 우회하고 불필요한 리렌더링을 막는 최적의 아키텍처 패턴은 무엇인가?
  • useCallbackuseMemo를 오남용하여 성능 오버헤드를 유발하는 주요 안티패턴은 무엇이며, 프로파일링을 통해 메모이제이션의 손익분기점을 어떻게 판단할 수 있는가?
  • React Context API의 상태 업데이트로 발생하는 전역적인 리렌더링 폭포(Re-render Cascade) 현상을 방지하기 위해, Context 분리(Splitting)와 메모이제이션 기법을 어떻게 조합해야 하는가?

Practical Application Contexts

  • Implementation: 계산 비용이 높은 필터링 로직에 useMemo를 적용하고, 하위의 메모이제이션된 컴포넌트로 전달되는 이벤트 핸들러에는 useCallback을 적용해 참조 안정성을 보장한다 [12, 27]. JSX 내의 익명 함수 사용을 지양하여 렌더링마다 새로운 참조가 생성되는 것을 막는다 [9, 28].
  • System Design: 프로젝트에 React Compiler를 도입하기 전, 코드베이스가 "Rules of React"를 준수하도록 eslint-plugin-react-hooks를 설정하여 린팅을 통해 코드를 통제한다 [17].
  • Operation / Maintenance: 지속적인 성능 모니터링을 위해 개발 중에는 React Profiler와 why-did-you-render를 사용해 불필요한 렌더링을 잡고, 프로덕션 환경에서는 INP(Interaction to Next Paint) 같은 Core Web Vitals 지표를 추적해 메모이제이션의 실질적 이점을 확인한다 [22-24, 29].
  • Learning Path: 리렌더링의 4가지 주요 원인(State, Props, Context, Parent Render)을 파악하고 [30], 얕은 비교의 원리를 학습한 뒤 React.memo, useMemo의 수동 적용법을 거쳐 React Compiler의 동작 원리를 이해하는 순서로 학습한다 [3, 7, 30].
  • My Project Relevance: 복잡한 대시보드나 리스트 등에서 데이터를 필터링하거나 UI와 상호작용할 때 발생하는 심각한 화면 끊김 현상과 메모리 누수를 해결하기 위해 이 원리들을 도입할 수 있다 [12, 31].

Adjacent Topics

  • Code Splitting & Lazy Loading
    • 확장 방향: 메모이제이션이 런타임에 불필요한 컴포넌트 재렌더링을 방지한다면, 이 기법은 애플리케이션 초기 로드 시 JavaScript 번들 크기를 줄여 성능을 끌어올리는 렌더링/로딩 아키텍처 최적화 기법이다 [7, 32, 33].
  • State Management Libraries (Zustand/Jotai)
    • 확장 방향: 잦은 상태 변경으로 인한 Context API의 광범위한 리렌더링을 방지하기 위해, 메모이제이션 대신 Selector(선택자) 패턴을 사용하여 상태의 특정 부분만 구독하게 만드는 대안적 상태 관리 기법이다 [34, 35].
  • Concurrent Rendering
    • 확장 방향: 메모이제이션으로도 해결하기 벅찬 무거운 UI 렌더링이 발생할 때, useTransitionuseDeferredValue를 사용하여 렌더링의 우선순위를 조정하고 UI의 반응성을 유지하는 진보된 성능 최적화 방법론이다 [36-38].

Last updated: 2026-04-30