state 변경은 리렌더링을 유발하며, 잘못 구조화된 state는 필요한 것보다 훨씬 많은 리렌더링을 일으켜 앱을 느리게 만들 수 있습니다. 핵심 전략: 좁게 구독하고, state를 분할하고, 메모이즈하고, state를 local로 유지하세요.
1. 필요한 최소 슬라이스만 구독하기
jsx
// ❌ 전체 store를 구독 → 어떤 변경이든 리렌더링
const store = ();
count = store.;
count = ( s.);
state 변경은 리렌더링을 유발하며, 잘못 구조화된 state는 필요한 것보다 훨씬 많은 리렌더링을 일으켜 앱을 느리게 만들 수 있습니다. 핵심 전략: 좁게 구독하고, state를 분할하고, 메모이즈하고, state를 local로 유지하세요.
// ❌ 전체 store를 구독 → 어떤 변경이든 리렌더링
const store = ();
count = store.;
count = ( s.);
선택적 구독(Zustand/Redux selector, Jotai atom)이 가장 큰 지렛대입니다 — 컴포넌트는 모든 store 갱신이 아니라 자신의 특정 슬라이스가 바뀔 때만 리렌더링됩니다.
// ❌ 하나의 큰 context → 무엇이든 바뀌면 모든 consumer가 리렌더링
// ✅ 별도의 context로 분리하여 무관한 갱신이 서로를 유발하지 않게 함
<ThemeContext><UserContext>...</UserContext></ThemeContext>
const Item = React.memo(ItemComponent); // props가 안 바뀌면 리렌더링 건너뜀
const expensiveValue = useMemo(() => compute(data), [data]);
const handleClick = useCallback(() => doX(id), [id]); // 안정적인 함수 참조
memoization은 자식의 props가(참조 기준) 안 바뀌면 자식의 리렌더링을 막습니다 — 하지만 props/콜백을 참조적으로 안정되게 유지하고(그래서 useCallback/useMemo) state를 immutable하게 갱신할 때만 작동합니다.
트리 상단의 state는 그 아래 모든 것을 리렌더링합니다.
state를 그것이 필요한 가장 작은 컴포넌트로 아래로 옮기세요 → 리렌더링 범위 축소.
// ❌ 매 렌더링마다 새 객체/배열은 하위 메모이제이션을 깨뜨림
<Child config={{ a: 1 }} /> // 매 렌더링마다 새 {}
// ✅ 안정적인 참조
const config = useMemo(() => ({ a: 1 }), []);
const selectActive = createSelector([s => s.items], items => items.filter(i => i.active));
// items가 안 바뀌면 같은 참조를 반환 → 불필요한 리렌더링 없음
React DevTools Profiler / "why did you render" → 무엇이 실제로 리렌더링되는지
최적화 전에 찾으세요. 성급한 메모이제이션은 이득 없이 복잡성만 더합니다.
과도한 리렌더링은 굼뜬 React/state 기반 앱의 주된 원인이며, state가 어떻게 구조화되고 구독되는지에서 비롯됩니다.
도구 모음 — 좁은 선택적 구독, context 분할, 안정적인 참조와 함께하는 메모이제이션, immutable 갱신, state localizing — 은 리렌더링 범위를 직접 제어합니다.
결정적으로, 무신경한 메모이제이션은 이득 없이 복잡성을 더하므로 최적화 전에 (Profiler로) 측정하여 실제 병목을 찾아야 합니다.
무엇이 리렌더링을 유발하는지, 그리고 그것을 어떻게 억제하는지 이해하는 것은 성능 좋은 대규모 앱에 필수적입니다.