Files
2nd/10_Wiki/Topics/Coding/Perf_React_Reconciler.md
T
2026-05-09 21:08:02 +09:00

6.3 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
perf-react-reconciler React Reconciler / Rendering — 측정 / 최적화 Coding draft B conceptual 2026-05-09 2026-05-09
performance
react
reconciler
vibe-coding
language applicable_to
TS / React
Frontend
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)

// 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 });
}
function MyComponent(...) { ... }
MyComponent.whyDidYouRender = true;

→ Console 에 "Same value but new reference" 같은 알람.

Common 문제 1: inline object/array

// ❌
<Child config={{ x: 1 }} items={[1, 2]} />

// ✅
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)

// ❌
<Child onClick={() => doSomething(id)} />

// ✅ — child 가 memo 인 경우만 의미 있음
const handle = useCallback(() => doSomething(id), [id]);
<Child onClick={handle} />

⚠️ child memo 아니면 의미 X.

memo (compare props)

const Row = memo(function Row({ item }: { item: Item }) {
  return <div>{item.name}</div>;
});

// Custom comparator
const Row = memo(Component, (prev, next) => prev.item.id === next.item.id);

Context — split

// ❌ 한 context — 어떤 변경도 모든 consumer
<AppContext.Provider value={{ user, theme, settings }}>

// ✅ 분리
<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    <SettingsContext.Provider value={settings}>

→ Theme 변경 = User consumer 재렌더 X.

State 위치 (lift up vs push down)

// ❌ 큰 page state — 작은 input 변경이 page 전체 re-render
function Page() {
  const [text, setText] = useState('');
  return (
    <>
      <Input value={text} onChange={setText} />
      <BigList />
      <Sidebar />
    </>
  );
}

// ✅ State 가 사용 위치만
function Input() {
  const [text, setText] = useState('');
  return <input value={text} onChange={(e) => setText(e.target.value)} />;
}

List + key

// ❌ index = key — reorder 시 잘못된 reuse
{items.map((it, i) => <Row key={i} item={it} />)}

// ✅
{items.map(it => <Row key={it.id} item={it} />)}

useDeferredValue / startTransition

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 — 언제?

// ✅ 무거운 계산
const sorted = useMemo(() => bigArray.sort(), [bigArray]);

// ✅ memo 자식의 props
const handle = useCallback(() => ..., [dep]);
<MemoChild onClick={handle} />

// ❌ 가벼운 + 일반 자식
const x = useMemo(() => a + b, [a, b]);  // 비용 > 절약

React Compiler (19+, 자동)

// babel.config.js
plugins: ['babel-plugin-react-compiler'];

→ memo / useMemo / useCallback 자동 — 수동 거의 불필요.

Render count (custom hook)

function useRenderCount(name: string) {
  const c = useRef(0);
  c.current++;
  console.log(`${name} render #${c.current}`);
}

Suspense + lazy = 실제 사용

// 큰 component → 다른 chunk
const Heavy = lazy(() => import('./Heavy'));

<Suspense fallback={<Skeleton />}>
  <Heavy />
</Suspense>

List virtualization

import { useVirtualizer } from '@tanstack/react-virtual';

const v = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 50,
  overscan: 5,
});

return (
  <div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
    <div style={{ height: v.getTotalSize() }}>
      {v.getVirtualItems().map(vr => (
        <div key={vr.key} style={{ position: 'absolute', top: vr.start, height: vr.size }}>
          {items[vr.index].name}
        </div>
      ))}
    </div>
  </div>
);

→ 1만 row 도 빠름.

Selector pattern (Zustand / Redux)

// ❌ 모든 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.

🔗 관련 문서