[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-09 21:08:02 +09:00
parent f0befc887a
commit 93ec7e9056
363 changed files with 68333 additions and 64 deletions
@@ -0,0 +1,108 @@
---
id: react-virtualization-lists
title: React List 가상화 (Virtualization)
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [react, virtualization, lists, performance, vibe-coding]
tech_stack: { language: "TypeScript / @tanstack/react-virtual", applicable_to: ["Web", "React Native"] }
applied_in: []
aliases: [windowing, react-virtual, react-window, infinite scroll]
---
# React List 가상화
> 1만 행 모두 렌더하면 DOM 폭발 → 60fps 깨짐. **화면에 보이는 30개만 렌더 + 위/아래 padding 으로 스크롤바 유지** = windowing. 1만 행도 60fps.
## 📖 핵심 개념
- 가상 list: 보이는 영역(viewport) + overscan 만 실제 DOM.
- Total height = `itemCount * itemHeight` 로 가짜 padding.
- 스크롤 시 보이는 영역 계산 → 그 인덱스 범위만 render.
## 💻 코드 패턴
### @tanstack/react-virtual
```tsx
import { useVirtualizer } from '@tanstack/react-virtual';
function BigList({ items }: { items: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const v = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60, // 평균 행 높이
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: v.getTotalSize(), position: 'relative' }}>
{v.getVirtualItems().map(vi => (
<div
key={items[vi.index].id}
data-index={vi.index}
ref={v.measureElement} // 가변 높이 측정
style={{
position: 'absolute',
top: 0, left: 0, width: '100%',
transform: `translateY(${vi.start}px)`,
}}
>
<Row data={items[vi.index]} />
</div>
))}
</div>
</div>
);
}
```
### 무한 스크롤 + virtualization
```tsx
const { items, fetchNextPage, hasNextPage } = useInfiniteQuery(...);
const flat = items?.pages.flatMap(p => p.items) ?? [];
useEffect(() => {
const last = v.getVirtualItems().at(-1);
if (!last) return;
if (last.index >= flat.length - 5 && hasNextPage) fetchNextPage();
}, [v.getVirtualItems(), hasNextPage]);
```
### Grid (2D)
```tsx
const colVirt = useVirtualizer({ count: cols, ..., horizontal: true });
const rowVirt = useVirtualizer({ count: rows, ... });
// 두 차원 모두 windowing
```
## 🤔 의사결정 기준
| 항목 수 | 가상화 |
|---|---|
| < 50 | 불필요 |
| 50-500 | 선택. 모바일이면 권장 |
| 500+ | 필수 |
| 가변 높이 | measureElement 또는 ResizeObserver |
| 그리드 (행+열) | 2D virtualizer |
| Drag-and-drop | 가상화 + DnD 통합 — react-arborist, dnd-kit |
## ❌ 안티패턴
- **모든 list 가상화**: 작은 list 도 가상화 비용. 50+ 부터.
- **estimateSize 부정확**: 스크롤바 점프. 평균 + measureElement 권장.
- **overscan 0**: 빠른 스크롤 시 빈 화면. 5~10 권장.
- **각 item 안의 무거운 컴포넌트 + memo 없음**: 빠른 스크롤 시 끊김. memo + stable props.
- **inner div 에 transform 외 layout 속성**: 위치 계산 깨짐. translate3d 권장.
- **Tab/검색 정확 위치 못 잡음**: scrollToIndex 제공.
- **SEO 필요한 list 가상화**: SSR 시 화면 외 행 없음. SSR 은 처음 N개 + hydration 후 가상화.
## 🤖 LLM 활용 힌트
- "@tanstack/react-virtual 선호 (RR/Next 호환). FlashList 는 RN".
- 무한 스크롤은 useInfiniteQuery + virtualizer 결합.
## 🔗 관련 문서
- [[React_Rendering_Optimization]]
- [[Backpressure_Patterns]]