61 lines
9.6 KiB
Markdown
61 lines
9.6 KiB
Markdown
# [[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]. 따라서 `useMemo`와 `useCallback`은 이러한 값과 함수의 참조를 안정적으로 유지하여 하위 컴포넌트의 불필요한 렌더링을 방지하는 데 핵심적인 역할을 한다 [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
|
|
|
|
### Related Concepts
|
|
|
|
#### [아키텍처/기반 기술]
|
|
- [[React Compiler]]
|
|
- 연결 이유: 수동 메모이제이션의 복잡성을 줄이고 빌드 타임에 자동으로 세밀한 메모이제이션 로직을 삽입하여 성능을 최적화하는 도구이다 [3, 4].
|
|
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 개발자가 수동으로 작성하던 `useMemo`와 `useCallback`이 빌드 프로세스 내에서 어떻게 추상화되고 최적화되는지 파악할 수 있다 [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의 한계를 우회하고 불필요한 리렌더링을 막는 최적의 아키텍처 패턴은 무엇인가?
|
|
- `useCallback`과 `useMemo`를 오남용하여 성능 오버헤드를 유발하는 주요 안티패턴은 무엇이며, 프로파일링을 통해 메모이제이션의 손익분기점을 어떻게 판단할 수 있는가?
|
|
- 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 렌더링이 발생할 때, `useTransition` 및 `useDeferredValue`를 사용하여 렌더링의 우선순위를 조정하고 UI의 반응성을 유지하는 진보된 성능 최적화 방법론이다 [36-38].
|
|
|
|
---
|
|
*Last updated: 2026-04-30* |