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.
Definição
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 → Solução
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.Dica de entrevista
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.Código
// 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
}Perguntas de entrevista
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← →