Tabelas e listas muito grandes podem reduzir significativamente o desempenho do site. A virtualização pode ajudar!
O react-window
é uma biblioteca que permite a renderização eficiente de listas grandes.
Confira um exemplo de uma lista com 1.000 linhas sendo renderizada com
react-window
. Tente rolar o mais rápido possível.
Por que isso é útil?
Às vezes, é necessário mostrar uma tabela ou lista grande com muitas linhas. Carregar todos os itens de uma lista assim pode afetar muito o desempenho.
Virtualização de lista, ou "janelamento", é o conceito de renderizar apenas o que está visível para o usuário. O número de elementos renderizados no início é um subconjunto muito pequeno da lista inteira, e a "janela" de conteúdo visível se move quando o usuário continua rolando a tela. Isso melhora a renderização e o desempenho de rolagem da lista.

Os nós do DOM que saem da "janela" são reciclados ou substituídos imediatamente por elementos mais recentes à medida que o usuário rola a lista para baixo. Isso mantém o número de todos os elementos renderizados específicos para o tamanho da janela.
react-window
react-window
é uma pequena biblioteca de terceiros que facilita a
criação de listas virtualizadas no seu aplicativo. Ele oferece várias APIs básicas
que podem ser usadas para diferentes tipos de listas e tabelas.
Quando usar listas de tamanho fixo
Use o componente FixedSizeList
se você tiver uma lista longa e unidimensional de itens do mesmo tamanho.
import React from 'react';
import { FixedSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const ListComponent = () => (
<FixedSizeList
height={500}
width={500}
itemSize={120}
itemCount={items.length}
>
{Row}
</FixedSizeList>
);
export default ListComponent;
- O componente
FixedSizeList
aceita uma propriedadeheight
,width
eitemSize
para controlar o tamanho dos itens na lista. - Uma função que renderiza as linhas é transmitida como um elemento filho para
FixedSizeList
. Os detalhes sobre o item específico podem ser acessados com o argumentoindex
(items[index]
). - Um parâmetro
style
também é transmitido ao método de renderização de linha, que precisa ser anexado ao elemento de linha. Os itens da lista são posicionados de forma absoluta com os valores de altura e largura atribuídos inline, e o parâmetrostyle
é responsável por isso.
O exemplo do Glitch mostrado anteriormente neste artigo mostra um exemplo de um componente
FixedSizeList
.
Quando usar listas de tamanho variável
Use o componente VariableSizeList
para renderizar uma lista de itens com tamanhos diferentes. Esse componente funciona da mesma forma que uma lista de tamanho fixo, mas
espera uma função para a propriedade itemSize
em vez de um valor específico.
import React from 'react';
import { VariableSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const getItemSize = index => {
// return a size for items[index]
}
const ListComponent = () => (
<VariableSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={getItemSize}
>
{Row}
</VariableSizeList>
);
export default ListComponent;
A incorporação a seguir mostra um exemplo desse componente.
A função de tamanho do item transmitida à propriedade itemSize
randomiza as alturas das linhas
neste exemplo. Em um aplicativo real, no entanto, deve haver uma lógica real
definindo os tamanhos de cada item. O ideal é que esses tamanhos sejam calculados com base em dados ou obtidos de uma API.
Grades
O react-window
também oferece suporte à virtualização de listas ou grades multidimensionais. Nesse contexto, a "janela" de conteúdo visível muda conforme o usuário rola a tela na horizontal e na vertical.

Da mesma forma, os componentes FixedSizeGrid
e VariableSizeGrid
podem ser usados dependendo se o tamanho de itens específicos da lista pode variar.
- Para
FixedSizeGrid
, a API é quase a mesma, mas com o fato de que alturas, larguras e contagens de itens precisam ser representadas para colunas e linhas. - Para
VariableSizeGrid
, as larguras das colunas e as alturas das linhas podem ser alteradas transmitindo funções em vez de valores às respectivas propriedades.
Consulte a documentação para ver exemplos de grades virtualizadas.
Carregamento lento ao rolar
Muitos sites melhoram o desempenho esperando para carregar e renderizar itens em uma lista longa até que o usuário role para baixo. Essa técnica, comumente chamada de "carregamento infinito", adiciona novos nós DOM à lista conforme o usuário rola a tela além de um determinado limite próximo ao final. Embora isso seja melhor do que carregar todos os itens de uma lista de uma só vez, ainda acaba preenchendo o DOM com milhares de entradas de linha se o usuário tiver rolado além disso. Isso pode levar a um tamanho excessivamente grande do DOM, o que começa a afetar o desempenho ao tornar os cálculos de estilo e as mutações do DOM mais lentos.
O diagrama a seguir pode ajudar a resumir isso:

A melhor abordagem para resolver esse problema é continuar usando uma biblioteca como
react-window
para manter uma pequena "janela" de elementos em uma página, mas também
carregar entradas mais recentes de forma lenta à medida que o usuário rola para baixo. Um pacote separado, react-window-infinite-loader
, torna isso possível com react-window
.
Considere o seguinte trecho de código, que mostra um exemplo de estado gerenciado em um componente App
principal.
import React, { Component } from 'react';
import ListComponent from './ListComponent';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // instantiate initial list here
moreItemsLoading: false,
hasNextPage: true
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
// method to fetch newer entries for the list
}
render() {
const { items, moreItemsLoading, hasNextPage } = this.state;
return (
<ListComponent
items={items}
moreItemsLoading={moreItemsLoading}
loadMore={this.loadMore}
hasNextPage={hasNextPage}
/>
);
}
}
export default App;
Um método loadMore
é transmitido para um ListComponent
filho que contém a
lista de carregamento infinito. Isso é importante porque o carregador infinito precisa
acionar um callback para carregar mais itens depois que o usuário rola a tela e passa de um determinado
ponto.
Confira como o ListComponent
que renderiza a lista pode ficar:
import React from 'react';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from "react-window-infinite-loader";
const ListComponent = ({ items, moreItemsLoading, loadMore, hasNextPage }) => {
const Row = ({ index, style }) => (
{/* define the row component using items[index] */}
);
const itemCount = hasNextPage ? items.length + 1 : items.length;
return (
<InfiniteLoader
isItemLoaded={index => index < items.length}
itemCount={itemCount}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
height={500}
width={500}
itemCount={itemCount}
itemSize={120}
onItemsRendered={onItemsRendered}
ref={ref}
>
{Row}
</FixedSizeList>
)}
</InfiniteLoader>
)
};
export default ListComponent;
Aqui, o componente FixedSizeList
está envolvido no InfiniteLoader
.
As propriedades atribuídas ao carregador são:
isItemLoaded
: método que verifica se um determinado item foi carregado.itemCount
: número de itens na lista (ou esperado)loadMoreItems
: callback que retorna uma promessa que é resolvida com dados adicionais para a lista
Uma prop de renderização é usada para retornar uma função que o componente de lista usa para renderizar.
Os atributos onItemsRendered
e ref
precisam ser transmitidos.
Confira a seguir um exemplo de como o carregamento infinito pode funcionar com uma lista virtualizada.
Rolar para baixo na lista pode parecer a mesma coisa, mas agora uma solicitação é feita para recuperar 10 usuários de uma API de usuário aleatório sempre que você rola perto do final da lista. Tudo isso é feito renderizando apenas uma "janela" de resultados por vez.
Ao verificar o index
de um determinado item, um estado de carregamento diferente pode ser
mostrado para um item, dependendo se uma solicitação foi feita para entradas mais recentes
e se o item ainda está carregando.
Exemplo:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Overscanning
Como os itens em uma lista virtualizada só mudam quando o usuário rola a tela, um espaço em branco pode aparecer brevemente quando novas entradas estão prestes a ser exibidas. Você pode tentar rolar rapidamente qualquer um dos exemplos anteriores neste guia para perceber isso.
Para melhorar a experiência do usuário com listas virtualizadas, o react-window
permite
fazer overscan de itens com a propriedade overscanCount
. Isso permite definir quantos itens fora da "janela" visível serão renderizados o tempo todo.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
funciona para os componentes FixedSizeList
e VariableSizeList
e tem um valor padrão de 1. Dependendo do tamanho de uma lista e de cada item, o overscanning de mais de uma entrada pode ajudar a evitar um flash perceptível de espaço vazio quando o usuário rola a tela. No entanto, fazer uma varredura excessiva de muitas entradas pode afetar negativamente a performance. O objetivo de usar uma lista virtualizada é minimizar o número de entradas para o que o usuário pode ver a qualquer momento. Portanto, tente manter o número de itens verificados em excesso o mais baixo possível.
Para FixedSizeGrid
e VariableSizeGrid
, use as propriedades overscanColumnsCount
e
overscanRowsCount
para controlar o número de colunas e linhas a serem
supervarreduras, respectivamente.
Conclusão
Se você não souber por onde começar a virtualizar listas e tabelas no seu aplicativo, siga estas etapas:
- Meça o desempenho de renderização e rolagem. Este artigo mostra como o medidor de FPS no Chrome DevTools pode ser usado para analisar a eficiência da renderização de itens em uma lista.
- Inclua
react-window
em listas ou grades longas que estejam afetando o desempenho. - Se houver recursos que não são compatíveis com
react-window
, usereact-virtualized
se você não puder adicionar essa funcionalidade por conta própria. - Encapsule sua lista virtualizada com
react-window-infinite-loader
se precisar carregar itens de forma lenta à medida que o usuário rola a tela. - Use a propriedade
overscanCount
para suas listas e as propriedadesoverscanColumnsCount
eoverscanRowsCount
para suas grades e evite um flash de conteúdo vazio. Não faça uma varredura excessiva de muitas entradas, porque isso vai afetar negativamente a performance.