Las tablas y listas muy grandes pueden ralentizar significativamente el rendimiento de tu sitio. La virtualización puede ayudar.
react-window es una biblioteca que permite renderizar listas grandes de manera eficiente.
¿Por qué es útil?
Es posible que, en ocasiones, debas mostrar una tabla o lista grande que contenga muchas filas. Cargar cada elemento de esa lista puede afectar el rendimiento de manera significativa.
La virtualización de listas, o "ventanas", es el concepto de renderizar solo lo que es visible para el usuario. La cantidad de elementos que se renderizan al principio es un subconjunto muy pequeño de la lista completa, y la "ventana" de contenido visible se mueve cuando el usuario continúa desplazándose. Esto mejora el rendimiento de renderización y desplazamiento de la lista.
Los nodos DOM que salen de la "ventana" se reciclan o se reemplazan de inmediato por elementos más nuevos a medida que el usuario se desplaza hacia abajo en la lista. Esto mantiene la cantidad de todos los elementos renderizados específicos para el tamaño de la ventana.
react-window
react-window es una pequeña biblioteca de terceros que facilita la creación de listas virtualizadas en tu aplicación. Proporciona varias APIs base que se pueden usar para diferentes tipos de listas y tablas.
Cuándo usar listas de tamaño fijo
Usa el componente FixedSizeList si tienes una lista larga y unidimensional de elementos del mismo tamaño.
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;
- El componente
FixedSizeListacepta una propiedadheight,widthyitemSizepara controlar el tamaño de los elementos dentro de la lista. - Se pasa una función que renderiza las filas como un elemento secundario a
FixedSizeList. Se puede acceder a los detalles sobre el elemento en particular con elindexargumento (items[index]). - También se pasa un parámetro
styleal método de renderización de filas que debe adjuntarse al elemento de fila. Los elementos de la lista se posicionan de forma absoluta con sus valores de altura y ancho asignados en línea, y el parámetrostylees responsable de esto.
Cuándo usar listas de tamaño variable
Usa el componente VariableSizeList para renderizar una lista de elementos que tienen diferentes tamaños. Este componente funciona de la misma manera que una lista de tamaño fijo, pero, en cambio, espera una función para la propiedad itemSize en lugar de un 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;
La función de tamaño de elemento que se pasa a la propiedad itemSize aleatoriza las alturas de las filas en este ejemplo. Sin embargo, en una aplicación real, debería haber una lógica real que defina los tamaños de cada elemento. Lo ideal es que estos tamaños se calculen en función de los datos o se obtengan de una API.
Cuadrículas
react-window también proporciona compatibilidad para virtualizar listas o cuadrículas multidimensionales. En este contexto, la "ventana" de contenido visible cambia a medida que el usuario se desplaza horizontal y verticalmente.
Del mismo modo, se pueden usar los componentes FixedSizeGrid y VariableSizeGrid según si el tamaño de los elementos de lista específicos puede variar.
- Para
FixedSizeGrid, la API es casi la misma, pero con el hecho de que las alturas, los anchos y los recuentos de elementos deben representarse para las columnas y las filas. - Para
VariableSizeGrid, los anchos de columna y las alturas de fila se pueden cambiar pasando funciones en lugar de valores a sus propiedades respectivas.
Consulta la documentación para ver ejemplos de cuadrículas virtualizadas.
Carga diferida en el desplazamiento
Muchos sitios web mejoran el rendimiento esperando a cargar y renderizar elementos en una lista larga hasta que el usuario se haya desplazado hacia abajo. Esta técnica, que suele denominarse "carga infinita", agrega nodos DOM nuevos a la lista a medida que el usuario pasa un umbral determinado cerca del final. Aunque esto es mejor que cargar todos los elementos de una lista a la vez, termina propagando el DOM con miles de entradas de filas si el usuario se desplazó más allá de esa cantidad. Esto puede generar un tamaño de DOM excesivamente grande, lo que comienza a afectar el rendimiento al hacer que los cálculos de estilo y las mutaciones de DOM sean más lentos.
El siguiente diagrama puede ayudar a resumir esto:
El mejor enfoque para resolver este problema es seguir usando una biblioteca como react-window para mantener una pequeña "ventana" de elementos en una página, pero también cargar de forma diferida las entradas más recientes a medida que el usuario se desplaza hacia abajo. Un paquete independiente,
react-window-infinite-loader, hace que esto sea posible con react-window.
Considera el siguiente fragmento de código que muestra un ejemplo de estado que se administra en un componente App superior.
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;
Se pasa un método loadMore a un ListComponent secundario que contiene la lista de cargador infinito. Esto es importante porque el cargador infinito debe activar una devolución de llamada para cargar más elementos una vez que el usuario haya pasado un punto determinado.
Así es como se puede ver el ListComponent que renderiza la lista:
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;
Aquí, el componente FixedSizeList se incluye en InfiniteLoader.
Las propiedades asignadas al cargador son las siguientes:
isItemLoaded: Es el método que verifica si se cargó un elemento determinado.itemCount: Es la cantidad de elementos de la lista (o esperada).loadMoreItems: Es la devolución de llamada que muestra una promesa que se resuelve en datos adicionales para la lista.
Se usa una
propiedad de renderización
para mostrar una función que usa el componente de lista para renderizar.
Los atributos onItemsRendered y ref son atributos que deben pasarse.
Desplazarse hacia abajo en la lista puede sentirse igual, pero ahora se realiza una solicitud para recuperar 10 usuarios de una API de usuario aleatoria cada vez que te desplazas cerca del final de la lista. Todo esto se realiza mientras solo se renderiza una sola "ventana" de resultados a la vez.
Si verificas el index de un elemento determinado, se puede mostrar un estado de carga diferente para un elemento, según si se realizó una solicitud para entradas más recientes y si el elemento aún se está cargando.
Por ejemplo:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Overscanning
Dado que los elementos de una lista virtualizada solo cambian cuando el usuario se desplaza, el espacio en blanco puede parpadear brevemente cuando se están por mostrar las entradas más recientes. Puedes intentar desplazarte rápidamente por cualquiera de los ejemplos anteriores de esta guía para notarlo.
Para mejorar la experiencia del usuario de las listas virtualizadas, react-window te permite realizar un overscanning de elementos con la propiedad overscanCount. Esto te permite definir cuántos elementos fuera de la "ventana" visible se renderizarán en todo momento.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount funciona para los componentes FixedSizeList y VariableSizeList, y tiene un valor predeterminado de 1. Según el tamaño de una lista y el tamaño de cada elemento, el overscanning de más de una entrada puede ayudar a evitar un parpadeo notable de espacio vacío cuando el usuario se desplaza. Sin embargo, el overscanning de demasiadas entradas puede afectar el rendimiento de forma negativa. El objetivo principal de usar una lista virtualizada es minimizar la cantidad de entradas a lo que el usuario puede ver en un momento determinado, por lo que debes intentar mantener la cantidad de elementos con overscanning lo más baja posible.
Para FixedSizeGrid y VariableSizeGrid, usa las propiedades overscanColumnsCount y overscanRowsCount para controlar la cantidad de columnas y filas que se deben realizar con overscanning, respectivamente.
Conclusión
Si no sabes por dónde empezar a virtualizar listas y tablas en tu aplicación, sigue estos pasos:
- Mide el rendimiento de renderización y desplazamiento. En este artículo, se muestra cómo se puede usar el medidor de FPS en las Herramientas para desarrolladores de Chrome para explorar la eficiencia con la que se renderizan los elementos en una lista.
- Incluye
react-windowpara cualquier lista o cuadrícula larga que afecte el rendimiento. - Si hay ciertas funciones que no son compatibles con
react-window, considera usarreact-virtualizedsi no puedes agregar esta funcionalidad por tu cuenta. - Incluye tu lista virtualizada con
react-window-infinite-loadersi necesitas cargar elementos de forma diferida a medida que el usuario se desplaza. - Usa la propiedad
overscanCountpara tus listas y las propiedadesoverscanColumnsCountyoverscanRowsCountpara tus cuadrículas para evitar un parpadeo de contenido vacío. No realices un overscanning de demasiadas entradas, ya que esto afectará el rendimiento de forma negativa.