f8b21af4be
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>
187 lines
5.9 KiB
Markdown
187 lines
5.9 KiB
Markdown
---
|
||
id: wiki-2026-0508-대규모-데이터-렌더링-및-가상화-최적화
|
||
title: 대규모 데이터 렌더링 및 가상화 최적화
|
||
category: 10_Wiki/Topics
|
||
status: verified
|
||
canonical_id: self
|
||
aliases: [Virtualization, Windowing, Virtual List, 가상 스크롤]
|
||
duplicate_of: none
|
||
source_trust_level: A
|
||
confidence_score: 0.9
|
||
verification_status: applied
|
||
tags: [frontend, virtualization, performance, react, rendering]
|
||
raw_sources: []
|
||
last_reinforced: 2026-05-10
|
||
github_commit: pending
|
||
tech_stack:
|
||
language: typescript
|
||
framework: react
|
||
---
|
||
|
||
# 대규모 데이터 렌더링 및 가상화 최적화
|
||
|
||
## 매 한 줄
|
||
> **"매 보이는 것만 그린다"**. 100k row를 한 번에 mount 하면 DOM이 죽는다. Windowing 으로 viewport 안 ~30개만 render → scroll 시 recycle. 2026 표준은 TanStack Virtual / react-virtuoso, content-visibility CSS, 그리고 React 19의 `<Activity>` (offscreen).
|
||
|
||
## 매 핵심
|
||
|
||
### 매 왜 가상화가 필수인가
|
||
- DOM node 1개 ≈ 1-5KB memory + layout/paint cost.
|
||
- 10k+ row mount → 수백 MB heap, scroll jank, INP > 500ms.
|
||
- React reconciliation도 O(n) — fiber tree가 거대해지면 commit이 느려진다.
|
||
|
||
### 매 windowing 전략
|
||
- **Fixed size**: row height 동일 → offset = index × height. 가장 빠름.
|
||
- **Variable size**: row 마다 다름 → measured cache + estimated. ResizeObserver 로 동적 측정.
|
||
- **Dynamic load**: scroll 끝 도달 시 fetch (infinite scroll) — virtualization 과 결합.
|
||
|
||
### 매 응용
|
||
1. Admin table 100k+ row.
|
||
2. Chat history (역방향 virtualization).
|
||
3. Image gallery / Pinterest grid.
|
||
4. Code editor (Monaco-style line virtualization).
|
||
|
||
## 💻 패턴
|
||
|
||
### TanStack Virtual (2026 표준)
|
||
```tsx
|
||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||
|
||
function VirtualList({ items }: { items: Row[] }) {
|
||
const parentRef = useRef<HTMLDivElement>(null);
|
||
const virtualizer = useVirtualizer({
|
||
count: items.length,
|
||
getScrollElement: () => parentRef.current,
|
||
estimateSize: () => 48,
|
||
overscan: 5,
|
||
});
|
||
|
||
return (
|
||
<div ref={parentRef} className="h-[600px] overflow-auto">
|
||
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
|
||
{virtualizer.getVirtualItems().map(v => (
|
||
<div
|
||
key={v.key}
|
||
data-index={v.index}
|
||
ref={virtualizer.measureElement}
|
||
style={{
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
width: '100%',
|
||
transform: `translateY(${v.start}px)`,
|
||
}}
|
||
>
|
||
{items[v.index].name}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Variable height with measurement
|
||
```tsx
|
||
const virtualizer = useVirtualizer({
|
||
count: rows.length,
|
||
getScrollElement: () => parentRef.current,
|
||
estimateSize: () => 80, // 매 best guess
|
||
measureElement: el => el.getBoundingClientRect().height, // 매 actual measure
|
||
overscan: 8,
|
||
});
|
||
```
|
||
|
||
### CSS content-visibility (zero-JS virtualization)
|
||
```css
|
||
.row {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: 0 48px; /* 매 placeholder size */
|
||
}
|
||
```
|
||
브라우저가 viewport 밖 element를 자동으로 skip layout/paint. React 없이도 효과.
|
||
|
||
### Infinite scroll + virtualization
|
||
```tsx
|
||
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||
queryKey: ['rows'],
|
||
queryFn: ({ pageParam = 0 }) => fetchPage(pageParam),
|
||
getNextPageParam: last => last.nextCursor,
|
||
});
|
||
|
||
const allRows = data?.pages.flatMap(p => p.items) ?? [];
|
||
const virtualizer = useVirtualizer({ count: allRows.length, /* ... */ });
|
||
|
||
useEffect(() => {
|
||
const last = virtualizer.getVirtualItems().at(-1);
|
||
if (last && last.index >= allRows.length - 5 && hasNextPage) {
|
||
fetchNextPage();
|
||
}
|
||
}, [virtualizer.getVirtualItems()]);
|
||
```
|
||
|
||
### Grid virtualization (2D)
|
||
```tsx
|
||
const rowVirtualizer = useVirtualizer({ count: rows, /* ... */ });
|
||
const colVirtualizer = useVirtualizer({
|
||
horizontal: true,
|
||
count: cols,
|
||
getScrollElement: () => parentRef.current,
|
||
estimateSize: () => 120,
|
||
});
|
||
// 매 nested loop: rows × cols 의 visible cells only.
|
||
```
|
||
|
||
### React 19 `<Activity>` for tab-like UI
|
||
```tsx
|
||
<Activity mode={isActive ? 'visible' : 'hidden'}>
|
||
<ExpensiveTab /> {/* 매 hidden 시 unmount X, but no render cost */}
|
||
</Activity>
|
||
```
|
||
|
||
### Server-side pagination + cursor
|
||
```ts
|
||
// 매 절대 client 에 100k row 보내지 마라.
|
||
GET /api/rows?cursor=abc123&limit=50
|
||
→ { items: [...], nextCursor: 'def456' }
|
||
```
|
||
|
||
## 매 결정 기준
|
||
| 상황 | Approach |
|
||
|---|---|
|
||
| < 100 rows | 매 plain map. Virtualization X. |
|
||
| 100-1000 rows, fixed height | content-visibility CSS. |
|
||
| 1000+ rows | TanStack Virtual / react-virtuoso. |
|
||
| Variable height | measure + estimateSize. |
|
||
| Infinite stream | virtualization + cursor pagination. |
|
||
| Tab/accordion | React 19 `<Activity>`. |
|
||
|
||
**기본값**: TanStack Virtual + React Query infinite + content-visibility 보조.
|
||
|
||
## 🔗 Graph
|
||
- 부모: [[Frontend Performance]] · [[Rendering]]
|
||
- 변형: [[Infinite Scroll]] · [[Pagination]]
|
||
- 응용: [[Code Editor]]
|
||
- Adjacent: [[React Query]] · [[ResizeObserver]]
|
||
|
||
## 🤖 LLM 활용
|
||
**언제**: row count > 500, scroll 성능 issue, INP regression.
|
||
**언제 X**: static page, 매 작은 list (< 100), SEO-critical content (crawler 가 못 봄).
|
||
|
||
## ❌ 안티패턴
|
||
- **Mount all then hide**: `display: none` 으로 숨겨도 DOM 비용은 낸다.
|
||
- **Key as index**: row reorder 시 reconciliation 깨짐 — stable id 사용.
|
||
- **Inline measure on every render**: ResizeObserver + cache 필수.
|
||
- **No overscan**: scroll 시 빈 영역 보임. overscan 5-10.
|
||
- **Animation on virtualized rows**: 매 recycle 시 animation 초기화 — layout id 따로 관리.
|
||
|
||
## 🧪 검증 / 중복
|
||
- Verified (TanStack Virtual docs, Chrome DevTools, web.dev/content-visibility).
|
||
- 신뢰도 A.
|
||
|
||
## 🕓 Changelog
|
||
| 날짜 | 변경 |
|
||
|---|---|
|
||
| 2026-05-08 | Phase 1 |
|
||
| 2026-05-10 | Manual cleanup — virtualization + content-visibility + Activity 통합 |
|