--- 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(null); const v = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, // 평균 행 높이 overscan: 5, }); return (
{v.getVirtualItems().map(vi => (
))}
); } ``` ### 무한 스크롤 + 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]]