React & Frontend

Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção

11 min de leitura

Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção

O Problema da Renderização em Listas Grandes 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. 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. Entendendo react-window O Conceito Core React-window é uma biblioteca que implementa virtualização através

<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 &#039;react-window&#039;;

const MyList = () =&gt; {

const items = Array.from({ length: 10000 }, (_, i) =&gt; ({

id: i,

name: Item ${i}

}));

const Row = ({ index, style }) =&gt; (

&lt;div style={style} className=&quot;list-item&quot;&gt;

&lt;span&gt;{items[index].name}&lt;/span&gt;

&lt;span&gt;{items[index].id}&lt;/span&gt;

&lt;/div&gt;

);

return (

&lt;List

height={600}

itemCount={items.length}

itemSize={35}

width=&quot;100%&quot;

&gt;

{Row}

&lt;/List&gt;

);

};

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 &#039;react-window&#039;;

const VariableHeightList = () =&gt; {

const items = Array.from({ length: 10000 }, (_, i) =&gt; ({

id: i,

text: Item ${i},

height: 35 + (i % 3) * 15 // Alguns itens maiores

}));

const itemSizes = new Map(items.map(item =&gt; [item.id, item.height]));

const getItemSize = (index) =&gt; itemSizes.get(items[index].id);

const Row = ({ index, style }) =&gt; (

&lt;div

style={{

...style,

backgroundColor: index % 2 === 0 ? &#039;#f5f5f5&#039; : &#039;#fff&#039;

}}

className=&quot;list-item&quot;

&gt;

{items[index].text}

&lt;/div&gt;

);

return (

&lt;List

height={600}

itemCount={items.length}

itemSize={getItemSize}

width=&quot;100%&quot;

&gt;

{Row}

&lt;/List&gt;

);

};

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 &#039;@tanstack/react-virtual&#039;;

import { useRef } from &#039;react&#039;;

const VirtualList = () =&gt; {

const parentRef = useRef();

const items = Array.from({ length: 10000 }, (_, i) =&gt; ({

id: i,

name: Item ${i}

}));

const virtualizer = useVirtualizer({

count: items.length,

getScrollElement: () =&gt; parentRef.current,

estimateSize: () =&gt; 35,

overscan: 10 // Buffer de 10 itens acima e abaixo

});

const virtualItems = virtualizer.getVirtualItems();

return (

&lt;div

ref={parentRef}

style={{

height: &#039;600px&#039;,

overflow: &#039;auto&#039;

}}

&gt;

&lt;div

style={{

height: ${virtualizer.getTotalSize()}px,

width: &#039;100%&#039;,

position: &#039;relative&#039;

}}

&gt;

{virtualItems.map(virtualItem =&gt; (

&lt;div

key={virtualItem.key}

style={{

position: &#039;absolute&#039;,

top: 0,

left: 0,

width: &#039;100%&#039;,

height: ${virtualItem.size}px,

transform: translateY(${virtualItem.start}px)

}}

&gt;

&lt;div className=&quot;list-item&quot;&gt;

{items[virtualItem.index].name}

&lt;/div&gt;

&lt;/div&gt;

))}

&lt;/div&gt;

&lt;/div&gt;

);

};

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 &#039;@tanstack/react-virtual&#039;;

import { useInfiniteQuery } from &#039;@tanstack/react-query&#039;;

import { useRef } from &#039;react&#039;;

const InfiniteVirtualList = () =&gt; {

const parentRef = useRef();

const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =

useInfiniteQuery({

queryKey: [&#039;items&#039;],

queryFn: async ({ pageParam = 0 }) =&gt; {

const res = await fetch(/api/items?offset=${pageParam});

return res.json();

},

getNextPageParam: (lastPage, pages) =&gt;

lastPage.hasMore ? pages.length * 50 : undefined,

initialPageParam: 0

});

const allItems = data?.pages.flatMap(page =&gt; page.items) ?? [];

const virtualizer = useVirtualizer({

count: hasNextPage ? allItems.length + 1 : allItems.length,

getScrollElement: () =&gt; parentRef.current,

estimateSize: () =&gt; 50,

overscan: 10,

measureElement: typeof window !== &#039;undefined&#039;

? element =&gt; 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 &gt;= allItems.length - 1 &amp;&amp;

hasNextPage &amp;&amp;

!isFetchingNextPage

) {

fetchNextPage();

}

return (

&lt;div

ref={parentRef}

style={{ height: &#039;600px&#039;, overflow: &#039;auto&#039; }}

&gt;

&lt;div

style={{

height: ${virtualizer.getTotalSize()}px,

width: &#039;100%&#039;,

position: &#039;relative&#039;

}}

&gt;

{virtualItems.map(virtualItem =&gt; (

&lt;div

key={virtualItem.index}

style={{

position: &#039;absolute&#039;,

top: 0,

left: 0,

width: &#039;100%&#039;,

height: ${virtualItem.size}px,

transform: translateY(${virtualItem.start}px)

}}

&gt;

{virtualItem.index &lt; allItems.length ? (

&lt;div className=&quot;item&quot;&gt;{allItems[virtualItem.index].name}&lt;/div&gt;

) : isFetchingNextPage ? (

&lt;div className=&quot;loading&quot;&gt;Carregando...&lt;/div&gt;

) : null}

&lt;/div&gt;

))}

&lt;/div&gt;

&lt;/div&gt;

);

};

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 &#039;react-window&#039;;

import { useEffect, useState } from &#039;react&#039;;

const PerformanceDemo = () =&gt; {

const [renderTime, setRenderTime] = useState(0);

useEffect(() =&gt; {

const start = performance.now();

return () =&gt; {

const end = performance.now();

setRenderTime(end - start);

};

}, []);

const items = Array.from({ length: 50000 }, (_, i) =&gt; ({

id: i,

value: Math.random()

}));

const Row = ({ index, style }) =&gt; (

&lt;div style={style} className=&quot;perf-item&quot;&gt;

Item {items[index].id}: {items[index].value.toFixed(2)}

&lt;/div&gt;

);

return (

&lt;div&gt;

&lt;p&gt;Tempo de renderização inicial: {renderTime.toFixed(2)}ms&lt;/p&gt;

&lt;List

height={600}

itemCount={items.length}

itemSize={35}

width=&quot;100%&quot;

&gt;

{Row}

&lt;/List&gt;

&lt;/div&gt;

);

};

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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção
Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção

SWR em React: Estratégia Stale-While-Revalidate na Prática O que é SWR e por...

Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática
Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática

O Que São Componentes Controlados e Não Controlados? Antes de mais nada, prec...

Como Usar useImperativeHandle e forwardRef: Expondo APIs de Componentes em Produção
Como Usar useImperativeHandle e forwardRef: Expondo APIs de Componentes em Produção

O Problema: Componentes como Caixas Pretas Em React, componentes funcionais s...