Files
2nd/10_Wiki/Topics/Coding/React_Virtualization_Lists.md
T
2026-05-09 21:08:02 +09:00

3.6 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
react-virtualization-lists React List 가상화 (Virtualization) Coding draft B conceptual 2026-05-09 2026-05-09
react
virtualization
lists
performance
vibe-coding
language applicable_to
TypeScript / @tanstack/react-virtual
Web
React Native
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

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

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)

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 결합.

🔗 관련 문서