Tabelas e listas muito grandes podem diminuir significativamente a performance do seu site. A virtualização pode ajudar.
react-window é uma biblioteca que permite a renderização eficiente de listas grandes.
Por que isso é útil?
Às vezes, é necessário mostrar uma tabela ou lista grande que contenha muitas linhas. Carregar todos os itens em uma lista como essa pode afetar significativamente a performance.
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 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. Isso melhora a renderização e a performance 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 biblioteca pequena de terceiros que facilita a criação de listas virtualizadas no aplicativo. Ela oferece várias APIs de base 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 tamanho igual.
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
FixedSizeListaceita uma propriedadeheight,widtheitemSizepara 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 oindexargumento (items[index]). - Um parâmetro
styletambé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.
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 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 função de tamanho do item transmitida para a propriedade itemSize randomiza as alturas das linhas neste exemplo. No entanto, em um aplicativo real, precisa haver uma lógica real que defina os tamanhos de cada item. O ideal é que esses tamanhos sejam calculados com base em dados ou obtidos de uma API.
Grades
react-window também oferece suporte à virtualização de listas multidimensionais ou grades. Nesse contexto, a "janela" de conteúdo visível muda à medida que o usuário rola horizontalmente e verticalmente.
Da mesma forma, os componentes FixedSizeGrid e VariableSizeGrid podem ser usados dependendo se o tamanho de itens de lista específicos 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 para as respectivas propriedades.
Consulte a documentação para ver exemplos de grades virtualizadas.
Carregamento lento na rolagem
Muitos sites melhoram a performance 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 do DOM à lista à medida que o usuário rola além de um determinado limite próximo ao final. Embora isso seja melhor do que carregar todos os itens em 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 desse número. Isso pode levar a um tamanho de DOM excessivamente grande, o 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 lentamente novas entradas à 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 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 filho ListComponent que contém a lista de carregamento infinito. Isso é importante porque o carregador infinito precisa disparar um callback para carregar mais itens quando o usuário rolar além de um determinado ponto.
Confira como o ListComponent que renderiza a lista pode ser:
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 é encapsulado no 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 são atributos que precisam ser transmitidos.
A rolagem para baixo na lista pode parecer a mesma, mas agora uma solicitação é feita para recuperar 10 usuários de uma API de usuário aleatória 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, um espaço em branco pode aparecer brevemente quando novas entradas estão prestes a ser mostradas. 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 o overscan de itens com a propriedade overscanCount. Isso permite definir quantos itens fora da "janela" visível serão renderizados em todos os momentos.
<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, o overscanning de mais de uma entrada pode ajudar a evitar um flash perceptível de espaço vazio quando o usuário rola. No entanto, o overscanning 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 em um determinado momento. Portanto, tente manter o número de itens overscanned 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 overscanned, respectivamente.
Conclusão
Se você não tiver certeza de 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 FPS no Chrome DevTools pode ser usado para analisar a eficiência com que os itens são renderizados em uma lista.
- Inclua
react-windowpara listas ou grades longas que estejam afetando a performance. - Se houver determinados recursos indisponíveis em
react-window, considere usarreact-virtualizedse não for possível adicionar essa funcionalidade por conta própria. - Encapsule sua lista virtualizada com
react-window-infinite-loaderse precisar carregar itens lentamente à medida que o usuário rola. - Use a propriedade
overscanCountpara suas listas e as propriedadesoverscanColumnsCounteoverscanRowsCountpara suas grades para evitar um flash de conteúdo vazio. Não faça o overscan de muitas entradas, porque isso afetará negativamente a performance.