React 교재 · 고급 16편

React 성능 최적화 — memo · useMemo · useCallback

잘못 쓰면 오히려 손해. 측정 후 정확한 위치에 한 번씩만.

memoization 방패와 재렌더 카운터가 있는 UI 카드 일러스트 — 성능 최적화 컨셉

"React 가 느려요" 라는 질문에 90% 의 답은 — "진짜 느린지 측정부터". memo·useMemo·useCallback 을 모든 컴포넌트에 박는 건 안티패턴. 메모이제이션 자체도 비용이 있고, dependency 비교가 매 렌더 추가된다.

이번 16편은 3개 메모 도구의 정확한 동작 + 언제 써야 하고 언제 쓰지 말아야 하는지 + React Compiler (2025) 가 바꾸는 패러다임까지. 이걸 정확히 알면 React 가 빨라지는 게 아니라 빠른 React 를 망치지 않게 된다.

1. React 의 재렌더 모델 — 왜 최적화가 필요한가

React 기본 동작 — 부모가 재렌더되면 자식 전부 재렌더. props 가 안 바뀌어도. 대부분 빠르지만 무거운 컴포넌트 (큰 리스트·복잡 차트) 가 자식에 있으면 부모의 사소한 state 변화에 전부 재계산.

function Parent() { const [count, setCount] = useState(0); return ( <> <button onClick={() => setCount(c => c + 1)}>{count}</button> <HeavyList /> {/* count 클릭마다 재렌더 ❌ */} </> ); }

HeavyList 가 props 도 안 받는데 부모 state 바뀔 때마다 다시 그려진다. 여기에 memo 가 답.

2. memo — 컴포넌트의 재렌더 스킵

import { memo } from 'react'; const HeavyList = memo(function HeavyList({ items }) { console.log('HeavyList render'); // props 가 바뀔 때만 호출 return items.map(item => <Item key={item.id} {...item} />); });

memo 로 감싸면 React 가 props 를 얕은 비교 — 모든 prop 이 이전과 같으면 (참조 동일) 재렌더 스킵.

가장 흔한 함정 — props 에 객체/배열/함수가 있으면 memo 가 무력. 부모가 매 렌더마다 새 객체·함수를 만들어 전달 → 참조 항상 다름 → memo 비교가 항상 false. <HeavyList items={items.filter(...)} onClick={() => doSomething()} /> 같은 코드는 memo 가 있어도 매번 재렌더.

3. useMemo · useCallback — 참조 안정화

memo 의 함정 해결책. 객체/배열은 useMemo, 함수는 useCallback 으로 참조 유지.

function Parent() { const [count, setCount] = useState(0); const [filter, setFilter] = useState(''); // ❌ 매 렌더마다 새 배열 // const filtered = items.filter(i => i.name.includes(filter)); // ✅ filter 가 바뀔 때만 새 배열 const filtered = useMemo( () => items.filter(i => i.name.includes(filter)), [filter] ); // ❌ 매 렌더마다 새 함수 // const handleClick = (id) => deleteItem(id); // ✅ 안정적 참조 const handleClick = useCallback((id) => deleteItem(id), []); return <HeavyList items={filtered} onClick={handleClick} />; }

이 두 Hook 의 dependency 배열은 useEffect (11편) 와 동일 — 안에서 쓰는 값을 모두 포함. ESLint 가 자동 검사.

4. 사용 가이드 — 4 신호 + 측정 우선

실전에서 메모 도구를 박을 만한 4 신호.

도구박을 신호
memo자식 컴포넌트가 무거운데 부모가 자주 재렌더 (예: 입력 도중 매 키마다 차트 다시 그림)
useMemo계산 자체가 비싼 경우 (큰 리스트 정렬·필터·복잡 수식). 또는 그 결과를 memo 자식에 props 로 전달
useCallback함수를 memo 자식에 props 로 전달. 또는 useEffect dependency 로 사용
박지 말 것정상 속도. memo 의 비교 비용이 재렌더 비용보다 클 수 있음. "혹시 모르니" 박지 말 것
측정 도구 — React DevTools 의 Profiler 탭. 어떤 컴포넌트가 몇 번·얼마나 오래 렌더되는지 시각화. 무엇을 최적화할지 측정 후 결정. "느낌상 느림" 으로 박지 말 것.

5. React Compiler — 곧 위 3개가 불필요해진다

2024~2025 년 가장 큰 변화 — React Compiler (a.k.a. React Forget). 컴파일 시점에 자동으로 memoization 을 삽입. 개발자가 useMemo·useCallback·memo 를 박을 필요 없이 컴파일러가 알아서.

// React Compiler 이전 (지금까지의 코드) const filtered = useMemo(() => items.filter(...), [items]); const handleClick = useCallback(() => doIt(), []); // React Compiler 적용 후 const filtered = items.filter(...); // 컴파일러가 자동 memo const handleClick = () => doIt();

현재 (2026 년) — Meta·Instagram 내부에서 production 사용 중, OSS 는 stable 향해 진행. Next.js 15+ 와 Vite 플러그인 모두 옵트인 지원. 단계적으로 옵트인하다 결국 디폴트가 될 듯.

지금 행동 — Compiler 가 표준이 되기 전까지는 위 3 도구 알아야 한다. 라이브러리·기존 코드베이스가 다 쓰고 있고, 면접에서도 묻는다. 다만 새 코드를 미리 Compiler-friendly 하게 — Rules of React 준수 (mutate 금지, side effect 는 effect 안에서) 가 곧 호환성.

16편으로 React 의 핵심 8개 (5편 props · 6 state · 11 effect · 12 hook · 13 context · 14 reducer · 15 router · 16 perf) 가 정리됐다. 17편부터는 데이터 fetch 의 모던 표준 — Suspense + TanStack Query 로.

다음 글

React 교재 17편 — Suspense + 데이터 fetch. TanStack Query 로 useEffect fetch 의 진짜 졸업.

© 2026 주나이테크(주) @JUNAITECH