Virtualizar una lista es una muy buena herramienta para listados largos, se nota un gran mejora en performance y es muy sencillo con las librerias existentes. La mejor alternativa que encontre para realizarlo es react-window
Otro punto clave para mi es lo rapido/facil que se puede implementar, no lo descartaria de una, probalo y medi la performance.
Basicamente, la idea de virtualizar se trata de solo renderizar los elementos que el usuario veria en su pantalla/lo que el tamaño del listado permita y a medida que el usuario scrollea ir cambiando los items que se ven por los nuevos segun a donde scrollee.
No me voy a enfocar en explicar bien a detalle como funciona la virtualizacion, porque hay muchas librerias que lo tienen resuelto, aca vamos a aplicar esas librerias en nuestro listado, no implementar la virtualizacion de cero.
No es necesario virtualizar cada lista en tu pagina pero si tenes un listado que pueda crecer mucho, que tenga muchas acciones/estados en sus items o que veas que esta andando lento podes probar la virtualización.
Con las librerias que existen es muy facil de implementar para realizar una prueba y ver si ayuda a tu listado en particular. Podes tardar 10/15min en implementarlo, probar si mejora y quitarlo si no.
Aca un ejemplo, una lista generica en react
const Row = (rowProps) => {
return <li>{/* Row component */}</li>;
};
const RegularList = (items) => {
return (
<ul>
{items.map((item) => (
<Row key={item.id} {...item} />
))}
</ul>
);
};
Y la misma lista pero virtualizada con react-window
import { FixedSizeList as List } from 'react-window';
/*
index: 0, 1, 2
style: tenes que pasarsela al primer elemento html,
es importante para que funcione bien la virtualizacion.
data: el objeto que le pasamos desde el listado.
*/
const Row = ({ index, style, data }) => {
//obtenemos nuestro item con el indice
const rowProps = data.items[index]
return <li style={style}>{/* Row component */}</li>;
};
const VirtualList = (items) => (
<List
height={600} // Altura del listado en el DOM.
itemSize={100} // Altura de cada item(Nuestro componente "Row")
itemCount={items.length} // Cantidad de items en el listado
itemData={{
items,
// Aca podes pasar lo que quieras, cada row va a recibir este objecto
// Necesitas pasar el listado total, asi busca el indice en ese listado
}}
width="100%"
>
{Row}
</List>
);
# Yarn
yarn add react-window
# NPM
npm install react-window
Repo: https://github.com/bvaughn/react-window
Documentacion con ejemplos: https://react-window.vercel.app/#/examples/list/fixed-size
Puede sonar logico, de hecho es la mejora que propone la virtualizacion. Solo van a renderizarse los elementos que estan visibles en la lista. A medida que el usuario scrollea cambian los items que estan en el html.
Pero esto puede afectar a tu listado en algunos casos:
1. El usuario ya no va a poder buscar estos elementos apretando ctrl + f
en el navegador, no va a encontrar a los items que no estan visibles.
2. Esto puede causar problemas si tenes alguna funcion que busca obtener desde el html un item que no esta visible. Supongamos que tenes un boton de “Ir al final de la lista”, o si estamos en un chat, “Ir al ultimo mensaje”.
Si para resolver esto estabas buscando el ultimo elemento html y ejecutando scrollIntoView()
vas a tener problemas, ya que el ultimo elemento en el html no necesariamente es el ultimo del listado.
Este caso tenes que resolverlo con la libreria, que provee una funcion para scrollear a cierto item segun su indice. Aca la documentacion.
El componente de listado necesita una altura fija. Para ella necesitas una referencia de altura maxima, puede ser el tamaño de la pantalla menos algun header/footer de tu pagina. Y lo comparas contra la altura de los items que tenes.
const items = [1,2,3,4,5]
const ITEM_HEIGHT = 100
const HEADER_HEIGHT = 50
const listHeight = Math.min(window.innerHeight - HEADER_HEIGHT, items.length * ITEM_HEIGHT)
Es importante definir un maximo, si usas items.length * ITEM_HEIGHT
directamente no se esta renderizando, porque el listado es enorme y entran todos los items en el.
Sino podrias probar esta libreria del mismo creador, que toma la altura maxima disponible y la pasa para abajo.
const VirtualList = (items) => (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
width={width}
// ... etc
>
{Row}
</List>
)
</AutoSizer>;
);
https://www.npmjs.com/package/react-virtualized-auto-sizer
Tambien necesitas que los items tengan una altura fija, en un listado tradicional los items se renderizan y ocupan la altura de su contenido, aca necesitas controlar eso.
Se puede complicar por ejemplo para un chat, en donde tenes mensajes de distintos tamaños segun su contenido de texto.
Si no podes definir una altura fija se le agrega complejidad a tu virtualizacion. Para esto react-window
ofrece una VariableSizeList en donde el itemSize pasa a ser una funcion y tenes que encargarte del calculo de la altura.
import { VariableSizeList as List } from 'react-window';
const VirtualList = (items) => {
const getItemSize = index => {
const item = items[index]
// tu funcion magica para calcular la altura correspondiente
return magicHeightCalc(item) // number
}
return (
<List
height={600}
itemSize={getItemSize}
itemCount={items.length}
itemData={{
items,
}}
width="100%"
>
{Row}
</List>
)
};
Porque elegi react-window
? No hice un analisis profundo. Principalmente fue porque tuve que integrar la virtualizacion en un microfront que pertenece a una web que ya tenia agregada la dependencia.
react-window
fue hecha por el creador de react-virtualized
, historica libreria para virtualizar (?. La reescribio, solo con las funcionalidades esenciales, mas rapida y liviana.
Yo de todos modos yo iria react-window
, excepto que necesites alguna de las features de react-virtualized
.
Tambien otra libreria que existe es react-vtree
, orientada a virtualizar listas con hijos colapsables. La utilizaria para un caso en donde necesita una estructura de arbol virtualizada.
Y realmente existen muchas librerias, solo resaltaria ademas de estas a @tanstack/react-virtual
, porque el creador es el que hizo react-query, react-table y mas. No la use pero no tengo dudas que es una buena alternativa.
react-window
⇒ https://react-window.vercel.app/#/examples/list/fixed-size
react-virtualized
⇒ https://bvaughn.github.io/react-virtualized/#/components/List
react-vtree
⇒ https://github.com/Lodin/react-vtree
@tanstack/react-virtual
⇒ https://tanstack.com/virtual/latest
;9gustin 🤠