Tabelas e listas muito grandes podem reduzir significativamente o desempenho do site. A virtualização pode ajudar!
react-window
é uma
biblioteca que permite renderizar listas grandes com eficiência.
Veja um exemplo de lista que contém 1.000 linhas sendo renderizadas com react-window
. Tente rolar o mais rápido possível.
Por que isso é útil?
Pode haver momentos em que você precisa exibir uma tabela ou lista grande com muitas linhas. Carregar cada item dessa lista pode afetar significativamente o desempenho.
A virtualização de listas, ou "janelamento", é o conceito de renderizar apenas o que está visível para o usuário. O número de elementos renderizados no começo é um subconjunto muito pequeno de toda a lista, e a "janela" de conteúdo visível se move quando o usuário continua rolando a tela. Isso melhora a renderização e a performance de rolagem da lista.
Os nós DOM que saem da "janela" são reciclados ou imediatamente substituídos 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.
janela de reação
react-window
é uma pequena biblioteca de terceiros que facilita a criação de listas virtualizadas no 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 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 filha 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 linhas que precisa ser anexado ao elemento de linha. Os itens de lista são posicionados com os valores de altura e largura atribuídos inline, e o parâmetrostyle
é responsável por isso.
O exemplo de 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 que têm
tamanhos diferentes. Esse componente funciona da mesma maneira que uma lista de tamanhos fixos, 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 para a propriedade itemSize
randomiza as alturas das linhas
neste exemplo. No entanto, em um aplicativo real, é preciso haver uma lógica real definindo os tamanhos de cada item. O ideal é que esses tamanhos sejam calculados com base
em dados ou recebidos de uma API.
Grades
react-window
também é compatível com a virtualização de listas multidimensionais,
ou grades. Nesse contexto, a "janela" de conteúdo visível muda à medida que o usuário
rola a tela horizontalmente e verticalmente.
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 as alturas, larguras e contagens de itens precisam ser representadas para colunas e linhas. - Para
VariableSizeGrid
, as larguras das colunas e alturas das linhas podem ser alteradas transmitindo funções em vez de valores para as respectivas propriedades.
Confira a documentação para conferir exemplos de grades virtualizadas.
Carregamento lento ao rolar
Muitos sites melhoram o desempenho aguardando o carregamento e a renderização de itens em uma lista longa até que o usuário role para baixo. Essa técnica, geralmente conhecida como "carregamento infinito", adiciona novos nós DOM à lista à medida que o usuário rola a tela além de um determinado limite até o fim. Embora isso seja melhor do que carregar todos os itens em uma lista de uma só vez, isso ainda acaba preenchendo o DOM com milhares de entradas de linha caso o usuário tenha passado dessa quantidade. Isso pode levar a um tamanho excessivo do DOM, o que começa a afetar o desempenho, 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
para fazer o carregamento lento de entradas mais recentes à medida que o usuário rola para baixo. Um pacote separado,
react-window-infinite-loader
, torna isso possível com react-window
.
Considere o trecho de código abaixo, 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 a um ListComponent
filho que contém a
lista infinita de carregadores. Isso é importante porque o carregador infinito precisa disparar um callback para carregar mais itens depois que o usuário tiver rolado além de um determinado ponto.
Veja 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
é unido ao InfiniteLoader
.
Os acessórios atribuídos 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 em dados extras para a lista.
Uma
propriedade de renderização (link em inglês)
é usada para retornar uma função que o componente da lista usa para renderizar.
Os atributos onItemsRendered
e ref
são atributos que precisam ser
transmitidos.
Confira a seguir um exemplo de como o carregamento infinito pode funcionar com uma lista virtualizada.
A rolagem da lista para baixo pode parecer a mesma, mas agora é feita uma solicitação para recuperar 10 usuários de uma API de usuário aleatório sempre que você rola para perto do 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
mostrado 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
}
};
Overscaning
Como os itens em uma lista virtualizada só mudam quando o usuário rola a tela, o espaço em branco pode piscar brevemente à medida que as entradas mais recentes estão prestes a ser exibidas. Tente rolar rapidamente qualquer um dos exemplos anteriores deste guia para perceber isso.
Para melhorar a experiência do usuário de listas virtualizadas, react-window
permite
que você faça o overscan de itens com a propriedade overscanCount
. Isso permite
definir quantos itens fora da "janela" visível serão renderizados sempre.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
funciona para os componentes FixedSizeList
e VariableSizeList
e tem um valor padrão de 1. Dependendo do tamanho da lista
e do tamanho de cada item, a verificação excessiva de mais de uma entrada pode
ajudar a evitar um flash visível de espaço vazio quando o usuário rola a tela. No entanto,
o overscaning de muitas entradas pode afetar negativamente o desempenho. O objetivo
de usar uma lista virtualizada é minimizar o número de entradas no que
o usuário pode ver em um determinado momento. Portanto, tente manter o número de itens
verificados excessivamente o mínimo possível.
Para FixedSizeGrid
e VariableSizeGrid
, use as propriedades overscanColumnsCount
e overscanRowsCount
para controlar o número de colunas e linhas para overscan, respectivamente.
Conclusão
Se você não souber onde começar a virtualizar listas e tabelas no seu aplicativo, siga estas etapas:
- Medir o desempenho de renderização e rolagem. Este artigo mostra como o medidor de QPS no Chrome DevTools pode ser usado para explorar a eficiência com que os itens são renderizados em uma lista.
- Inclua
react-window
para listas ou grades longas que estejam afetando o desempenho. - Se houver determinados recursos sem suporte no
react-window
, usereact-virtualized
se não for possível adicionar essa funcionalidade por conta própria. - Una a lista virtualizada com
react-window-infinite-loader
se você precisar carregar itens lentamente conforme o usuário rola a tela. - Use a propriedade
overscanCount
para suas listas e as propriedadesoverscanColumnsCount
eoverscanRowsCount
para suas grades para evitar a exibição de conteúdo vazio. Não verifique muitas entradas em excesso, porque isso afetará negativamente o desempenho.