Las tablas y listas muy grandes pueden ralentizar significativamente el rendimiento de tu sitio. La virtualización puede ayudarte.
react-window
es una biblioteca que permite renderizar listas grandes de manera eficiente.
Este es un ejemplo de una lista que contiene 1,000 filas que se renderizan con react-window
. Intenta desplazarte lo más rápido posible.
¿Por qué es útil?
En ocasiones, es posible que debas mostrar una tabla o lista grande que contenga muchas filas. Cargar cada elemento de una lista de este tipo 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 toda la lista, 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 por la lista. Esto mantiene la cantidad de todos los elementos renderizados específicos del tamaño de la ventana.
react-window
react-window
es una biblioteca pequeña de terceros que facilita la creación de listas virtualizadas en tu aplicación. Proporciona varias APIs básicas que se pueden usar para diferentes tipos de listas y tablas.
Cuándo utilizar 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
FixedSizeList
acepta una propiedadheight
,width
yitemSize
para controlar el tamaño de los elementos de la lista. - Una función que renderiza las filas se pasa como elemento secundario a
FixedSizeList
. Se puede acceder a los detalles del elemento específico con el argumentoindex
(items[index]
). - También se pasa un parámetro
style
al método de renderización de filas que debe adjuntarse al elemento de fila. Los elementos de lista están absolutamente posicionados con sus valores de altura y ancho asignados intercalados, y el parámetrostyle
es responsable de esto.
El ejemplo de Glitch que se mostró antes en este artículo muestra un ejemplo de un componente FixedSizeList
.
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 su lugar, 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;
En la siguiente incorporación, se muestra un ejemplo de este componente.
La función de tamaño del 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. Idealmente, estos tamaños deberían calcularse
en función de los datos o obtenerse de una API.
Cuadrículas
react-window
también proporciona compatibilidad con la virtualización de listas multidimensionales o cuadrículas. En este contexto, la "ventana" del contenido visible cambia a medida que el usuario se desplaza horizontalmente y verticalmente.
Del mismo modo, los componentes FixedSizeGrid
y VariableSizeGrid
se pueden usar en función de si el tamaño de los elementos específicos de la lista puede variar.
- En el caso de
FixedSizeGrid
, la API es casi la misma, pero con el hecho de que las alturas, el ancho y el recuento de elementos deben representarse en las columnas y las filas. - En el caso de
VariableSizeGrid
, se pueden cambiar el ancho de las columnas y las alturas de las filas pasando funciones en lugar de valores a sus respectivos elementos.
Consulta la documentación para ver ejemplos de cuadrículas virtualizadas.
Carga diferida durante el desplazamiento
Muchos sitios web mejoran el rendimiento esperando a cargar y renderizar elementos en una lista larga hasta que el usuario se desplaza hacia abajo. Esta técnica, comúnmente conocida como "carga infinita", agrega nuevos nodos del DOM a la lista a medida que el usuario se desplaza por un umbral determinado cerca del final. Aunque esto es mejor que cargar todos los elementos de una lista a la vez, termina poblando 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 demasiado grande, lo que comienza a afectar el rendimiento, ya que ralentiza los cálculos de estilo y las mutaciones del DOM.
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 para cargar de forma diferida las entradas más nuevas a medida que el usuario se desplaza hacia abajo. Un paquete separado, react-window-infinite-loader
, hace esto 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 del cargador infinito. Esto es importante porque el cargador infinito necesita activar una devolución de llamada para cargar más elementos una vez que el usuario se desplaza más allá de un punto determinado.
Este es el aspecto que puede tener 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 une dentro de InfiniteLoader
.
Los accesorios asignados al cargador son los siguientes:
isItemLoaded
: Es un método que verifica si se cargó un elemento determinado.itemCount
: Cantidad de elementos en la lista (o esperados)loadMoreItems
: Devolución de llamada que muestra una promesa que se resuelve en datos adicionales para la lista.
Se usa una prop de renderización para mostrar una función que usa el componente de la lista para renderizar.
Los atributos onItemsRendered
y ref
son atributos que se deben pasar.
El siguiente es un ejemplo de cómo la carga infinita puede funcionar con una lista virtualizada.
Desplazarse hacia abajo en la lista puede parecer lo mismo, pero ahora se realiza una solicitud para recuperar 10 usuarios de una API de usuarios aleatorios cada vez que te desplazas cerca del final de la lista. Todo esto se hace mientras 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 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
}
};
Sobrebarrido
Dado que los elementos de una lista virtualizada solo cambian cuando el usuario se desplaza, el espacio en blanco puede parpadear brevemente cuando están a punto de mostrarse las entradas más recientes. Puedes desplazarte rápidamente por cualquiera de los ejemplos anteriores de esta guía para ver esto.
Para mejorar la experiencia del usuario de las listas virtualizadas, react-window
te permite realizar un análisis excesivo de los 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 exceso de escaneo de más de una entrada puede ayudar a evitar un destello notable de espacio vacío cuando el usuario se desplaza. Sin embargo, el análisis excesivo de demasiadas entradas puede afectar el rendimiento de forma negativa. El objetivo 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 intenta mantener la cantidad de elementos analizados en exceso 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 superponen, 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 la renderización y el 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 eficacia con la que se renderizan los elementos en una lista.
- Incluye
react-window
para las listas o cuadrículas largas que afecten el rendimiento. - Si hay ciertas funciones que no son compatibles con
react-window
, considera usarreact-virtualized
en caso de que no puedas agregar esta funcionalidad por tu cuenta. - Une tu lista virtualizada con
react-window-infinite-loader
si necesitas cargar elementos de forma diferida a medida que el usuario se desplaza. - Usa la propiedad
overscanCount
para tus listas y las propiedadesoverscanColumnsCount
yoverscanRowsCount
para tus cuadrículas para evitar que se muestre un contenido vacío. No realices un barrido excesivo de demasiadas entradas, ya que esto afectará el rendimiento de forma negativa.