R
Interview Deckreact · next.js · senior/staff
0·0·122
System Design

Virtualização de Listas Longas (Windowing)

Renderizar só o que está visível. react-window, react-virtual, TanStack Virtual para listas com milhares de itens.

Virtualização (windowing) renderiza apenas os elementos visíveis no viewport mais um buffer pequeno. Uma lista de 10.000 items renderiza ~20-30 nós no DOM em vez de 10.000. Dramática redução de memory uso e tempo de renderização inicial. Essencial para: feeds infinitos, tabelas de dados grandes, listas de produtos em e-commerce, logs em tempo real.

Problema
Lista com 1.000 items: 1.000 nós no DOM + 1.000 event listeners + todo o CSS calculado = scroll travado, memory leak, first render lento. Em mobile, 300 items já causam jank visível.
Solução
TanStack Virtual (agnóstico, 2024): calcular posições de todos os itens mas só renderizar os visíveis. react-window: componentes FixedSizeList (itens de altura igual) e VariableSizeList (alturas dinâmicas). Para tabelas: TanStack Table + Virtual. Para scroll infinito: combinar com Intersection Observer para carregar mais quando chega ao fim.
Tip
TanStack Virtual é a escolha moderna (2024) — suporta horizontal, grid, window scroller, e itens de tamanho variável com medição dinâmica. react-window ainda é mais leve e estável para casos simples. O maior desafio de variableSize é medir itens que ainda não foram renderizados — estimateSize() deve ser conservador (tamanho médio real) para evitar jumps de scroll.
// TanStack Virtual — moderno, flexible
import { useVirtualizer } from '@tanstack/react-virtual'

function VirtualList({ items }: { items: Product[] }) {
  const parentRef = useRef<HTMLDivElement>(null)

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 72, // altura estimada de cada item
    overscan: 5, // buffer de itens fora do viewport
  })

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      {/* Container com altura total para o scrollbar */}
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            ref={virtualizer.measureElement} // mede altura real
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualItem.start}px)`,
              width: '100%',
            }}
          >
            <ProductCard product={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  )
}

// Scroll infinito com React Query + TanStack Virtual
function InfiniteProductList() {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
    queryKey: ['products'],
    queryFn: ({ pageParam }) => fetchProducts(pageParam),
    getNextPageParam: (last) => last.nextCursor,
  })

  const items = data?.pages.flatMap(p => p.products) ?? []

  // Detectar quando o último item fica visível
  const lastItemRef = useCallback((node: HTMLDivElement | null) => {
    if (!node) return
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting && hasNextPage) fetchNextPage()
    })
    observer.observe(node)
    return () => observer.disconnect()
  }, [hasNextPage, fetchNextPage])

  // ... virtualizer setup
}
Q.Como virtualizar uma lista com alturas dinâmicas (texto de tamanho variável)?
A.TanStack Virtual com measureElement: usar um ref no elemento renderizado, o VirtualItem recebe `measureRef` para o virtual item medir após render. Internamente usa ResizeObserver. Passar `estimateSize` como fallback antes de medir. Ou react-window com VariableSizeList + CellMeasurer da react-virtualized. O approach do TanStack é mais moderno e não precisa de ref callback manual.
Q.Como combinar virtualização com scroll infinito?
A.Intersection Observer no último item visível (ou sentinel element após a lista): quando fica visível, disparar fetchNextPage() do React Query infinite query. O virtual list precisa saber o totalCount estimado para calcular o scrollbar. Padrão: `count = hasMore ? items.length + 1 : items.length` — o +1 é um placeholder que, quando fica visível, carrega mais.
Q.Virtualização funciona com Drag-and-Drop?
A.Difícil mas possível. O problema: itens saem do DOM durante o drag, quebrando a animação. Soluções: (1) desativar virtualização durante drag (se lista for pequena o suficiente), (2) usar dnd-kit com estratégia de virtualização específica, (3) manter items do drag 'pinados' no DOM fora da virtual window. @dnd-kit/sortable tem um exemplo com TanStack Virtual.
119/122← →