<h2>O Problema da Renderização em Listas Grandes</h2>
<p>Quando trabalha com listas contendo milhares de itens em React, a aplicação enfrenta um problema fundamental: o navegador precisa renderizar e manter na memória do DOM todos os elementos, mesmo aqueles que não estão visíveis na tela. Isso causa travamentos, consumo excessivo de memória e degrada drasticamente a experiência do usuário. Uma lista com 10 mil itens significa 10 mil componentes React instanciados, 10 mil elementos DOM e toda a bagagem computacional que isso carrega.</p>
<p>A virtualização de listas é a solução elegante para esse problema. A ideia é simples: renderize apenas os itens que estão visíveis na viewport do usuário, mais alguns itens adjacentes como buffer. Quando o usuário faz scroll, remova os itens que saíram da visão e adicione os novos. Dessa forma, o número de elementos DOM permanece constante e pequeno, independentemente do tamanho total da lista.</p>
<h2>Entendendo react-window</h2>
<h3>O Conceito Core</h3>
<p>React-window é uma biblioteca que implementa virtualização através de dois componentes principais: <code>FixedSizeList</code> para listas com itens de altura/largura fixa, e <code>VariableSizeList</code> para listas onde os itens têm tamanhos diferentes. A biblioteca é mantida por Brian Vaughn, um dos mantenedores do React, e é considerada a solução mais leve e performática disponível.</p>
<p>O funcionamento interno é baseado em calcular qual intervalo de itens deve estar visível baseado na posição de scroll. React-window monitora eventos de scroll, calcula o índice do primeiro e último item visível, e renderiza apenas aquele intervalo. O container mantém o scroll bar com a altura total correta, criando a ilusão de uma lista infinita.</p>
<h3>Implementando FixedSizeList</h3>
<pre><code class="language-jsx">import { FixedSizeList as List } from 'react-window';
const MyList = () => {
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: Item ${i}
}));
const Row = ({ index, style }) => (
<div style={style} className="list-item">
<span>{items[index].name}</span>
<span>{items[index].id}</span>
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</List>
);
};
export default MyList;</code></pre>
<p>Neste exemplo, a propriedade <code>style</code> passada pelo componente Row é crucial: ela contém <code>top</code> e <code>height</code> que posicionam o elemento absolutamente dentro da lista virtual. O <code>itemSize</code> de 35 pixels indica que cada item ocupa exatamente esse espaço, permitindo que react-window calcule precisamente qual intervalo renderizar. Com 10 mil itens, apenas cerca de 20 estarão no DOM em qualquer momento.</p>
<h3>Usando VariableSizeList</h3>
<pre><code class="language-jsx">import { VariableSizeList as List } from 'react-window';
const VariableHeightList = () => {
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
text: Item ${i},
height: 35 + (i % 3) * 15 // Alguns itens maiores
}));
const itemSizes = new Map(items.map(item => [item.id, item.height]));
const getItemSize = (index) => itemSizes.get(items[index].id);
const Row = ({ index, style }) => (
<div
style={{
...style,
backgroundColor: index % 2 === 0 ? '#f5f5f5' : '#fff'
}}
className="list-item"
>
{items[index].text}
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
>
{Row}
</List>
);
};
export default VariableHeightList;</code></pre>
<p>Quando itens têm alturas variáveis, você fornece uma função para <code>itemSize</code> que retorna a altura específica de cada item. React-window ainda virtualiza eficientemente, mas precisa fazer cálculos mais complexos. Para listas muito grandes com muita variação de tamanho, considere manter um cache das alturas ou estimar valores.</p>
<h2>Explorando react-virtual</h2>
<h3>Quando Usar react-virtual</h3>
<p>React-virtual é a biblioteca mantida por TanStack (criadores do React Query e React Table). Ela oferece uma API mais flexível e integra-se naturalmente com React Query e React Table. Enquanto react-window é uma solução completa e pronta para usar, react-virtual é mais um hook primitivo que você compõe com seus próprios elementos.</p>
<p>A abordagem é diferente: react-virtual fornece um hook <code>useVirtualizer</code> que gerencia a lógica de virtualização, deixando o rendering totalmente a seu cargo. Isso oferece mais controle mas requer mais código sua.</p>
<h3>Implementação Básica com useVirtualizer</h3>
<pre><code class="language-jsx">import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
const VirtualList = () => {
const parentRef = useRef();
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: Item ${i}
}));
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
overscan: 10 // Buffer de 10 itens acima e abaixo
});
const virtualItems = virtualizer.getVirtualItems();
return (
<div
ref={parentRef}
style={{
height: '600px',
overflow: 'auto'
}}
>
<div
style={{
height: ${virtualizer.getTotalSize()}px,
width: '100%',
position: 'relative'
}}
>
{virtualItems.map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: ${virtualItem.size}px,
transform: translateY(${virtualItem.start}px)
}}
>
<div className="list-item">
{items[virtualItem.index].name}
</div>
</div>
))}
</div>
</div>
);
};
export default VirtualList;</code></pre>
<p>Aqui vemos a abordagem de react-virtual: você passa um elemento de scroll como referência, define o tamanho estimado dos itens, e o hook fornece a lista de itens virtualizados. Você tem controle total sobre o HTML gerado e estilos aplicados. A propriedade <code>overscan</code> define quantos itens fora da viewport devem ser mantidos renderizados para suavizar o scroll.</p>
<h3>Integração com React Query</h3>
<pre><code class="language-jsx">import { useVirtualizer } from '@tanstack/react-virtual';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useRef } from 'react';
const InfiniteVirtualList = () => {
const parentRef = useRef();
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['items'],
queryFn: async ({ pageParam = 0 }) => {
const res = await fetch(/api/items?offset=${pageParam});
return res.json();
},
getNextPageParam: (lastPage, pages) =>
lastPage.hasMore ? pages.length * 50 : undefined,
initialPageParam: 0
});
const allItems = data?.pages.flatMap(page => page.items) ?? [];
const virtualizer = useVirtualizer({
count: hasNextPage ? allItems.length + 1 : allItems.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 10,
measureElement: typeof window !== 'undefined'
? element => element?.getBoundingClientRect().height
: undefined
});
const virtualItems = virtualizer.getVirtualItems();
const lastVirtualItem = virtualItems[virtualItems.length - 1];
// Fetch next page quando chegar perto do fim
if (
lastVirtualItem?.index >= allItems.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage();
}
return (
<div
ref={parentRef}
style={{ height: '600px', overflow: 'auto' }}
>
<div
style={{
height: ${virtualizer.getTotalSize()}px,
width: '100%',
position: 'relative'
}}
>
{virtualItems.map(virtualItem => (
<div
key={virtualItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: ${virtualItem.size}px,
transform: translateY(${virtualItem.start}px)
}}
>
{virtualItem.index < allItems.length ? (
<div className="item">{allItems[virtualItem.index].name}</div>
) : isFetchingNextPage ? (
<div className="loading">Carregando...</div>
) : null}
</div>
))}
</div>
</div>
);
};
export default InfiniteVirtualList;</code></pre>
<p>Essa integração mostra a força de react-virtual: combinar virtualização com dados que chegam incrementalmente. Você monitora quando o usuário se aproxima do fim da lista e dispara <code>fetchNextPage</code>. A virtualização mantém o performance mesmo com dezenas de milhares de itens acumulados.</p>
<h2>Comparação Prática e Escolhas</h2>
<h3>react-window vs react-virtual</h3>
<p>React-window é ideal quando você tem uma lista estática completa e quer a solução mais simples. A API é intuitiva, a documentação é clara, e funciona imediatamente. Use quando construir listas tradicionais, grades virtuais ou tabelas onde os dados estão todos disponíveis de uma vez.</p>
<p>React-virtual é preferível quando você precisa integração com React Query, quando tem dados que chegam incrementalmente, ou quando precisa de máximo controle sobre o rendering. A curva de aprendizado é um pouco maior, mas a flexibilidade compensa em cenários complexos.</p>
<h3>Métricas de Performance</h3>
<pre><code class="language-jsx">import { FixedSizeList as List } from 'react-window';
import { useEffect, useState } from 'react';
const PerformanceDemo = () => {
const [renderTime, setRenderTime] = useState(0);
useEffect(() => {
const start = performance.now();
return () => {
const end = performance.now();
setRenderTime(end - start);
};
}, []);
const items = Array.from({ length: 50000 }, (_, i) => ({
id: i,
value: Math.random()
}));
const Row = ({ index, style }) => (
<div style={style} className="perf-item">
Item {items[index].id}: {items[index].value.toFixed(2)}
</div>
);
return (
<div>
<p>Tempo de renderização inicial: {renderTime.toFixed(2)}ms</p>
<List
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</List>
</div>
);
};
export default PerformanceDemo;</code></pre>
<p>Teste esse código com 50 mil itens. Sem virtualização, o navegador congelaria. Com virtualização, a renderização inicial é praticamente instantânea, e o scroll é fluido. React-window típicamente renderiza 15-25 itens para uma viewport de 600px com itens de 35px, independentemente do total de itens. React-virtual tem overhead um pouco maior mas oferece mais flexibilidade.</p>
<h2>Conclusão</h2>
<p>Virtualização de listas é uma técnica essencial quando você trabalha com grandes volumes de dados em React. A escolha entre react-window e react-virtual depende do seu contexto: use react-window para listas estáticas simples e completas, use react-virtual quando integração com React Query ou máxima flexibilidade forem prioridades. Ambas reduzem significativamente o número de elementos DOM, transformando aplicações que seriam inutilizáveis em experiências fluidas e responsivas. O investimento em aprender essas bibliotecas se compensa rapidamente quando você encontra seus primeiros casos reais.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/bvaughn/react-window" target="_blank" rel="noopener noreferrer">React-window - Documentação Oficial</a></li>
<li><a href="https://tanstack.com/virtual/latest" target="_blank" rel="noopener noreferrer">React-virtual - TanStack Documentation</a></li>
<li><a href="https://kentcdodds.com/blog/virtual-lists-with-react-window" target="_blank" rel="noopener noreferrer">Virtualizing Large Lists with React by Kent C. Dodds</a></li>
<li><a href="https://web.dev/virtualize-long-lists-react-window/" target="_blank" rel="noopener noreferrer">React Performance Optimization: Virtualization</a></li>
<li><a href="https://tanstack.com/virtual/latest/docs/guide/introduction" target="_blank" rel="noopener noreferrer">TanStack Virtual - API Reference</a></li>
</ul>
<p><!-- FIM --></p>