stack-analysis
CHAPTER 99 / 120
읽기 약 2분
FUNCTION
프론트엔드 렌더링 최적화: 가상화 + memo
핵심 개념
React.memo·useMemo·virtual list·windowing — 1만 항목도 60fps.
본문
React 리렌더 원리
// 부모 리렌더 → 자식 모두 리렌더 (props 변화 무관)
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+</button>
<ExpensiveChild /> {/* 매번 리렌더 ❌ */}
</div>
);
}
// React.memo — props 변화 시만 리렌더
const ExpensiveChild = React.memo(() => {
console.log('render');
return <div>...</div>;
});useMemo / useCallback
function Component({ items }: { items: Item[] }) {
// ❌ 매 렌더마다 새 객체
const sorted = items.sort((a, b) => a.name.localeCompare(b.name));
// ✅ items 변화 시만
const sorted = useMemo(
() => items.toSorted((a, b) => a.name.localeCompare(b.name)),
[items]
);
// ❌ 매 렌더마다 새 함수 → 자식 memo 무효화
const handleClick = (id: string) => doSomething(id);
// ✅
const handleClick = useCallback((id: string) => {
doSomething(id);
}, []);
return <ChildList items={sorted} onClick={handleClick} />;
}언제 memo? — 측정 우선
React.memo 비용:
- props 비교 (얕은 비교 자동)
- 효과 미미한데 코드 복잡도 증가
언제 쓸 것인가:
1. 큰 리스트의 항목 (1000+)
2. 비싼 렌더 (차트·계산)
3. 자주 리렌더되는 부모의 자식
React DevTools Profiler로 측정 후 결정
"premature optimization is the root of all evil"가상화 (Virtual List)
// 1만 항목 → DOM 1만 → 느림
import { useVirtualizer } from '@tanstack/react-virtual';
function LongList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
<div
style={{
height: virtualizer.getTotalSize(),
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: virtualRow.size,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<ListItem item={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
// → DOM 10개만 (보이는 부분 + overscan)
// → 1만 항목도 60fpsReact Compiler (자동 최적화)
// React 19+ Compiler — 수동 memo 자동화
// .babelrc 또는 next.config.js
{
"plugins": [
"babel-plugin-react-compiler"
]
}
// 컴파일러가 자동 분석:
// - 어떤 값이 리렌더 시 변하는가
// - 자동으로 memoization 추가
// → 수동 useMemo/useCallback 불필요
// 단, 아직 베타 — 큰 프로젝트는 신중key prop 정확히
// ❌ index를 key로 — 순서 변경 시 깨짐
{items.map((item, i) => <Item key={i} {...item} />)}
// ✅ 안정적 ID
{items.map(item => <Item key={item.id} {...item} />)}
// 추가 항목, 정렬, 필터 시:
// ❌ index → 모든 항목 리렌더 + state 꼬임
// ✅ id → 변경된 항목만 리렌더큰 객체 prop 분해
// ❌ 큰 객체 통째로
<Component data={largeObject} />
// ✅ 필요한 것만
<Component name={largeObject.name} count={largeObject.count} />
// → memo 비교 빠름
// → 무관한 부분 변화 시 리렌더 XReact.lazy + Suspense
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
<Suspense fallback={<Skeleton />}>
<HeavyChart />
</Suspense>
// 라우트 단위
const Dashboard = lazy(() => import('./pages/Dashboard'));CSS — Avoid Reflow
/* ❌ 매 프레임 layout 트리거 */
.element {
transition: width 0.3s;
}
/* ✅ GPU 가속 (transform·opacity) */
.element {
transition: transform 0.3s;
}
/* will-change — GPU 힌트 (남발 X) */
.animated {
will-change: transform;
}Profiler API
import { Profiler } from 'react';
<Profiler id="App" onRender={(id, phase, actualDuration) => {
if (actualDuration > 16) { // 60fps = 16.67ms
console.warn(`Slow render: ${id} ${phase} ${actualDuration}ms`);
}
}}>
<App />
</Profiler>다음 챕터
CH.100 "성능 종합: Lighthouse 100점 달성 가이드".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 React 렌더 최적화 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
React 렌더 최적화 관련 베스트 프랙티스 5가지를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 React 렌더 최적화 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 시장의 React 렌더 최적화 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
프론트엔드 렌더링 최적화: 가상화 + memo는 이 3가지만 확실히 잡으세요
1.React.memo + useMemo + useCallback = 큰 리스트·비싼 컴포넌트만
2.가상화(@tanstack/react-virtual) = 1만+ 항목도 60fps
3.key는 안정적 ID — index는 순서 변경 시 깨짐
공유하기
진행도 99 / 120