--- id: wiki-2026-0508-불필요한-리렌더링-방지 title: 불필요한 리렌더링 방지 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Prevent Unnecessary Re-render, React 최적화, memoization, render optimization] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [frontend, react, performance, optimization, memoization] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: 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. **Memoization** — `React.memo`, `useMemo`, `useCallback`. 3. **Context split** — separate frequently-changing from stable. 4. **Component composition** — `children` 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 ```typescript 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
  • onClick(id)}>{name}
  • ; }); // 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 ```typescript import { useCallback, useState } from 'react'; function List({ items }: { items: { id: string; name: string }[] }) { const [selected, setSelected] = useState(null); // without useCallback — new ref each render → all Item re-render const handleClick = useCallback((id: string) => setSelected(id), []); return ( ); } ``` ### 3. useMemo for expensive computation ```typescript 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 ; } ``` ### 4. State colocation (lift down) ```typescript // BAD — input state at parent re-renders entire tree function App() { const [search, setSearch] = useState(''); return ( <> setSearch(e.target.value)} /> {/* re-renders on every keystroke */} ); } // GOOD — colocate input state in SearchInput function SearchInput({ onCommit }: { onCommit: (s: string) => void }) { const [v, setV] = useState(''); return { setV(e.target.value); onCommit(e.target.value); }} />; } function App() { return <>; } ``` ### 5. Context split (avoid frequent re-renders) ```typescript // 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(null); // rare change const ThemeCtx = createContext<'light'|'dark'>('light'); // user-toggled const CartCtx = createContext([]); // frequent // only consumers of changed context re-render ``` ### 6. Children as stable prop (composition) ```typescript // BAD — Wrapper re-renders => inner re-renders even if same JSX function App() { const [count, setCount] = useState(0); return {}; // 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
    {children}
    ; // children reference stable from parent } ``` ### 7. Zustand selector (subscribe slice) ```typescript 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 {count}; } // shallow compare for object selector function Profile() { const { name, id } = useStore(useShallow(s => ({ name: s.user.name, id: s.user.id }))); return {id}: {name}; } ``` ### 8. React Profiler (measure first) ```typescript import { Profiler } from 'react'; function onRender(id: string, phase: 'mount' | 'update', actualDuration: number) { if (actualDuration > 16) console.warn(`${id} ${phase} took ${actualDuration}ms`); } ``` ## 매 결정 기준 | 상황 | 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 - 부모: [[Memoization]] - 변형: [[React Compiler]] · [[Zustand]] - 응용: [[가상 스크롤]] · [[Virtualization]] ## 🤖 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**: `` 매 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 |