--- id: perf-react-reconciler title: React Reconciler / Rendering — 측정 / 최적화 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [performance, react, reconciler, vibe-coding] tech_stack: { language: "TS / React", applicable_to: ["Frontend"] } applied_in: [] aliases: [React reconciler, fiber, React Profiler, React Compiler, why-did-you-render, rerender] --- # React Reconciler / Rendering > "React 느림" 의 99% 가 over-render. **Profiler 측정 → key fix → memoization → split state**. **React Compiler (19+) 가 자동 memo**. ## 📖 핵심 개념 - Reconciler: virtual DOM diff. - Render: 함수 컴포넌트 호출. - Commit: DOM 변경 적용. - Re-render trigger: state 변경, parent re-render, context 변경. ## 💻 코드 패턴 ### React DevTools Profiler ``` Chrome DevTools → ⚛ Profiler → Record → 인터랙션 → Stop - Flame: 어떤 컴포넌트가 렌더 + 시간 - Ranked: 시간 정렬 - "Why did this render?" — props / state / hook 변경 ``` ### why-did-you-render (dev) ```ts // wdyr.ts (entry 첫 줄) import React from 'react'; import wdyr from '@welldone-software/why-did-you-render'; if (process.env.NODE_ENV === 'development') { wdyr(React, { trackAllPureComponents: true }); } ``` ```tsx function MyComponent(...) { ... } MyComponent.whyDidYouRender = true; ``` → Console 에 "Same value but new reference" 같은 알람. ### Common 문제 1: inline object/array ```tsx // ❌ // ✅ const config = useMemo(() => ({ x: 1 }), []); const items = useMemo(() => [1, 2], []); // 또는 외부 const const CONFIG = { x: 1 }; const ITEMS = [1, 2]; ``` ### Common 문제 2: inline function (children re-render) ```tsx // ❌ doSomething(id)} /> // ✅ — child 가 memo 인 경우만 의미 있음 const handle = useCallback(() => doSomething(id), [id]); ``` ⚠️ child memo 아니면 의미 X. ### memo (compare props) ```tsx const Row = memo(function Row({ item }: { item: Item }) { return
{item.name}
; }); // Custom comparator const Row = memo(Component, (prev, next) => prev.item.id === next.item.id); ``` ### Context — split ```tsx // ❌ 한 context — 어떤 변경도 모든 consumer // ✅ 분리 ``` → Theme 변경 = User consumer 재렌더 X. ### State 위치 (lift up vs push down) ```tsx // ❌ 큰 page state — 작은 input 변경이 page 전체 re-render function Page() { const [text, setText] = useState(''); return ( <> ); } // ✅ State 가 사용 위치만 function Input() { const [text, setText] = useState(''); return setText(e.target.value)} />; } ``` ### List + key ```tsx // ❌ index = key — reorder 시 잘못된 reuse {items.map((it, i) => )} // ✅ {items.map(it => )} ``` ### useDeferredValue / startTransition ```tsx function Search({ query }) { const deferred = useDeferredValue(query); // 무거운 계산은 deferred const results = useMemo(() => search(deferred), [deferred]); // input 은 빠름, results 는 약간 늦지만 — 안 막힘 } const [, startTransition] = useTransition(); startTransition(() => { setSearch(query); // 무거운 update 가 input 안 막음 }); ``` ### useMemo / useCallback — 언제? ```ts // ✅ 무거운 계산 const sorted = useMemo(() => bigArray.sort(), [bigArray]); // ✅ memo 자식의 props const handle = useCallback(() => ..., [dep]); // ❌ 가벼운 + 일반 자식 const x = useMemo(() => a + b, [a, b]); // 비용 > 절약 ``` ### React Compiler (19+, 자동) ```ts // babel.config.js plugins: ['babel-plugin-react-compiler']; ``` → memo / useMemo / useCallback 자동 — 수동 거의 불필요. ### Render count (custom hook) ```tsx function useRenderCount(name: string) { const c = useRef(0); c.current++; console.log(`${name} render #${c.current}`); } ``` ### Suspense + lazy = 실제 사용 ```tsx // 큰 component → 다른 chunk const Heavy = lazy(() => import('./Heavy')); }> ``` ### List virtualization ```tsx import { useVirtualizer } from '@tanstack/react-virtual'; const v = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, overscan: 5, }); return (
{v.getVirtualItems().map(vr => (
{items[vr.index].name}
))}
); ``` → 1만 row 도 빠름. ### Selector pattern (Zustand / Redux) ```tsx // ❌ 모든 state const { user, orders, cart } = useStore(); // ✅ selective const user = useStore(s => s.user); // shallow / deep equality const orderCount = useStore(s => s.orders.length); ``` → user 변경만 re-render. ## 🤔 의사결정 기준 | 증상 | 도구 | |---|---| | 모든 곳 느림 | Profiler 시작 | | 특정 input 끊김 | useDeferredValue / startTransition | | 큰 list | Virtualization | | 자주 re-render | wdyr + memo | | Context 변경 모두 영향 | Context split | | Production | React Compiler (자동) | ## ❌ 안티패턴 - **모든 거 memo / useCallback**: 비용 > 이득. compiler 또는 측정 후. - **Object literal 매 render**: useMemo 또는 외부 const. - **Index key**: reorder 시 잘못. - **Big context value 매번 새로 객체**: 모든 consumer re-render. - **무거운 작업 render 안**: useEffect 또는 useDeferredValue. - **State 너무 위 (lift up 과도)**: 모든 자식 re-render. ## 🤖 LLM 활용 힌트 - React DevTools Profiler 가 정답. - React 19 Compiler 자동 memo. - 큰 list = virtualizer. - Context = split. ## 🔗 관련 문서 - [[React_Rendering_Optimization]] - [[React_Virtualization_Lists]] - [[Web_Performance_Core_Vitals]]