Tabelas e listas muito grandes podem prejudicar significativamente o desempenho do seu site. A virtualização pode ajudar.
react-window
é uma
biblioteca que permite que listas grandes sejam renderizadas de maneira eficiente.
Confira um exemplo de uma lista que contém 1.000 linhas sendo renderizadas com
react-window
. Tente rolar a página o mais rápido possível.
Por que isso é útil?
Às vezes, você precisa mostrar uma tabela ou lista grande que contém muitas linhas. Carregar todos os itens dessa lista pode afetar significativamente o desempenho.
A virtualização de lista, ou "janelas", é o conceito de renderizar apenas o que fica visível para o usuário. O número de elementos renderizados inicialmente é um subconjunto muito pequeno de toda a lista, e a "janela" do conteúdo visível se move quando o usuário continua rolando. 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 imediatamente substituídos por elementos mais novos à 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 fornece 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 de tamanhos iguais.
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 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 para o 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 maneira 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 de linha
neste exemplo. No entanto, em um aplicativo real, precisa 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
multidimensional. Nesse contexto, a "janela" de conteúdo visível muda conforme o usuário
rola horizontalmente e verticalmente.
Da mesma forma, os componentes FixedSizeGrid
e VariableSizeGrid
podem ser usados
dependendo se o tamanho dos itens de lista específicos pode variar.
- Para
FixedSizeGrid
, a API é quase a mesma, mas com a diferença 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 para as respectivas propriedades.
Consulte a documentação para conferir 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 à medida que 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, ele ainda acaba preenchendo o DOM com milhares de entradas de linha se o usuário rolar para além deles. Isso pode levar a um tamanho de DOM excessivamente grande, que começa a afetar a performance, tornando 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 de forma lenta as entradas mais recentes conforme o usuário rola a página para baixo. Um pacote separado,
react-window-infinite-loader
, torna isso possível com react-window
.
Considere o seguinte código, que mostra um exemplo de estado
gerenciado em um componente App
pai.
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 carregadores infinitos. Isso é importante porque o loader infinito precisa
disparar um callback para carregar mais itens depois que o usuário rola a tela e passa de um determinado
ponto.
Confira como a 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
é unido ao InfiniteLoader
.
As propriedades atribuídas ao carregador são:
isItemLoaded
: método que verifica se um determinado item foi carregadoitemCount
: número de itens na lista (ou esperado)loadMoreItems
: callback que retorna uma promessa que é resolvida para dados adicionais da lista.
Uma
propriedade 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 a lista pode parecer o mesmo, mas agora uma solicitação é feita para recuperar 10 usuários de uma API de usuário aleatório sempre que você rola até o fim da lista. Isso é feito enquanto renderiza apenas uma única "janela" de resultados por vez.
Ao verificar o index
de um determinado item, um estado de carregamento diferente pode ser
exibido para um item, dependendo se uma solicitação foi feita para entradas mais recentes
e se o item ainda está sendo carregado.
Exemplo:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Overscan
Como os itens em uma lista virtualizada só mudam quando o usuário rola a tela, o espaço em branco pode piscar brevemente quando as entradas mais recentes estão prestes a aparecer. Você pode tentar rolar rapidamente qualquer um dos exemplos anteriores neste guia para notar isso.
Para melhorar a experiência do usuário com listas virtualizadas, react-window
permite
que você faça a varredura 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 do tamanho de cada item, a sobreposição 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,
a varredura de muitas entradas pode afetar negativamente o desempenho. O objetivo
de usar uma lista virtualizada é minimizar o número de entradas que
o usuário pode ver em um determinado momento. Portanto, tente manter o número de itens
com leituras excessivas 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
analisadas, respectivamente.
Conclusão
Se você não souber por onde começar a virtualizar listas e tabelas no aplicativo, siga estas etapas:
- Meça a performance de renderização e rolagem. Este artigo mostra como o medidor de QPS no Chrome DevTools pode ser usado para analisar a eficiência da renderização de itens em uma lista.
- Inclua
react-window
para listas ou grades longas que estejam afetando a performance. - Se houver recursos que não têm suporte em
react-window
, usereact-virtualized
se não conseguir adicionar essa funcionalidade. - Encapsule sua lista virtualizada com
react-window-infinite-loader
se precisar carregar itens de forma lenta conforme o usuário rola a tela. - Use a propriedade
overscanCount
para suas listas e as propriedadesoverscanColumnsCount
eoverscanRowsCount
para suas grades para evitar um flash de conteúdo vazio. Não faça a leitura de muitas entradas, porque isso afeta negativamente a performance.