Files
2nd/10_Wiki/Topics/Frontend/불필요한 리렌더링 방지.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

7.4 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-불필요한-리렌더링-방지 불필요한 리렌더링 방지 10_Wiki/Topics verified self
Prevent Unnecessary Re-render
React 최적화
memoization
render optimization
none A 0.9 applied
frontend
react
performance
optimization
memoization
2026-05-10 pending
language framework
typescript react

불필요한 리렌더링 방지

매 한 줄

"매 render 매 cheap by default — 매 measure first, 매 then memoize". React 매 default 의 parent render 의 child 의 모두 re-render — 매 보통 fast. 매 React Compiler (RC 2026) 의 auto-memoization 의 manual useMemo/useCallback 의 obsolescence 약속. 매 그 전 의 explicit memoization + state colocation.

매 핵심

매 re-render trigger

  • State change (useState, useReducer).
  • Props change (parent render).
  • Context change (consumer re-renders).
  • Parent re-render (default behavior).

매 4가지 전략

  1. State colocation — state 의 use 의 곳 가까이 (lift down).
  2. MemoizationReact.memo, useMemo, useCallback.
  3. Context split — separate frequently-changing from stable.
  4. Component compositionchildren prop 의 stable reference.

매 응용

  1. List rendering (10k+ items → virtualization + memo).
  2. Form state (controlled inputs → debounced or uncontrolled).
  3. Global state (Zustand selector, Redux useSelector with shallowEqual).
  4. Heavy computation (useMemo for derived state).

💻 패턴

1. React.memo for pure components

import { memo } from 'react';

interface ItemProps {
  id: string;
  name: string;
  onClick: (id: string) => void;
}

export const Item = memo(function Item({ id, name, onClick }: ItemProps) {
  console.log('render:', id);
  return <li onClick={() => onClick(id)}>{name}</li>;
});

// custom comparator (rarely needed)
export const ItemDeep = memo(Item, (prev, next) => prev.id === next.id && prev.name === next.name);

2. useCallback for stable function reference

import { useCallback, useState } from 'react';

function List({ items }: { items: { id: string; name: string }[] }) {
  const [selected, setSelected] = useState<string | null>(null);

  // without useCallback — new ref each render → all Item re-render
  const handleClick = useCallback((id: string) => setSelected(id), []);

  return (
    <ul>
      {items.map(it => <Item key={it.id} {...it} onClick={handleClick} />)}
    </ul>
  );
}

3. useMemo for expensive computation

import { useMemo } from 'react';

function Report({ rows }: { rows: Row[] }) {
  const stats = useMemo(() => {
    return {
      total: rows.reduce((a, r) => a + r.amount, 0),
      avg: rows.length ? rows.reduce((a, r) => a + r.amount, 0) / rows.length : 0,
      max: Math.max(...rows.map(r => r.amount)),
    };
  }, [rows]);

  return <Summary {...stats} />;
}

4. State colocation (lift down)

// BAD — input state at parent re-renders entire tree
function App() {
  const [search, setSearch] = useState('');
  return (
    <>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <ExpensiveTree />  {/* re-renders on every keystroke */}
    </>
  );
}

// GOOD — colocate input state in SearchInput
function SearchInput({ onCommit }: { onCommit: (s: string) => void }) {
  const [v, setV] = useState('');
  return <input value={v} onChange={e => { setV(e.target.value); onCommit(e.target.value); }} />;
}
function App() {
  return <><SearchInput onCommit={...} /><ExpensiveTree /></>;
}

5. Context split (avoid frequent re-renders)

// BAD — single context, everything re-renders on theme change
const AppCtx = createContext<{user: User; theme: 'light'|'dark'; cart: Item[]}>(...);

// GOOD — split by change frequency
const UserCtx = createContext<User | null>(null);  // rare change
const ThemeCtx = createContext<'light'|'dark'>('light');  // user-toggled
const CartCtx = createContext<Item[]>([]);  // frequent

// only consumers of changed context re-render

6. Children as stable prop (composition)

// BAD — Wrapper re-renders => inner re-renders even if same JSX
function App() {
  const [count, setCount] = useState(0);
  return <Wrapper>{<HeavyChild />}</Wrapper>; // HeavyChild created each render? No — JSX is reference
}

// pattern: pass children to escape parent re-render
function Wrapper({ children }: { children: ReactNode }) {
  const [internal, setInternal] = useState(0);
  return <div>{children}</div>; // children reference stable from parent
}

7. Zustand selector (subscribe slice)

import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';

const useStore = create<{ count: number; user: User; inc: () => void }>((set) => ({
  count: 0,
  user: { id: '1', name: 'A' },
  inc: () => set(s => ({ count: s.count + 1 })),
}));

// only re-renders when count changes (not user)
function Counter() {
  const count = useStore(s => s.count);
  return <span>{count}</span>;
}

// shallow compare for object selector
function Profile() {
  const { name, id } = useStore(useShallow(s => ({ name: s.user.name, id: s.user.id })));
  return <span>{id}: {name}</span>;
}

8. React Profiler (measure first)

import { Profiler } from 'react';

function onRender(id: string, phase: 'mount' | 'update', actualDuration: number) {
  if (actualDuration > 16) console.warn(`${id} ${phase} took ${actualDuration}ms`);
}

<Profiler id="List" onRender={onRender}>
  <List items={items} />
</Profiler>

매 결정 기준

상황 Approach
100s of items, fast props React.memo + useCallback.
10k+ items virtualization (react-window, TanStack Virtual).
Heavy compute useMemo.
Frequent global state Zustand selector / Redux Toolkit.
Form input uncontrolled (react-hook-form) or colocated state.
React 19+ RC compiler 의 auto-memo — manual 의 less needed.

기본값: 매 measure first (React DevTools Profiler). 매 colocate state. 매 React Compiler (when stable) 의 auto-memo. 매 manual memo 의 hot paths only.

🔗 Graph

🤖 LLM 활용

언제: measured perf bottleneck (Profiler), large list, heavy compute, frequent state updates. 언제 X: 매 not measured — premature optimization. 매 fast already (< 16ms render).

안티패턴

  • Memoize everything: useMemo 의 cost (deps compare) > savings 의 cheap compute.
  • Inline object/array as prop: <Comp data={{a:1}} /> 매 new ref 매 every render — defeats memo.
  • Context for everything: monolithic context → all consumers re-render.
  • useCallback w/o React.memo child: useless — child still re-renders on parent.
  • Premature optimization: optimize before measuring.
  • Mutating state directly: state.list.push(...) — React doesn't detect change.

🧪 검증 / 중복

  • Verified (React docs 2026, Kent C. Dodds patterns, React Compiler RFC).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — full content with 8 patterns + React Compiler 2026 note