Les tableaux et les listes trop volumineux peuvent ralentir de manière significative les performances de votre site. La virtualisation peut vous aider !
react-window
est une bibliothèque qui permet d'afficher efficacement des listes volumineuses.
Voici un exemple de liste contenant 1 000 lignes affichées avec react-window
. Faites défiler l'écran aussi vite que possible.
En quoi est-ce utile ?
Vous devrez peut-être afficher une grande table ou une longue liste contenant de nombreuses lignes. Le chargement de chaque élément d'une telle liste peut affecter considérablement les performances.
La virtualisation de liste, ou "fenêtrage", consiste à n'afficher que ce qui est visible par l'utilisateur. Le nombre d'éléments affichés au début correspond à un très petit sous-ensemble de la liste, et la "fenêtre" de contenu visible se déplace lorsque l'utilisateur continue de faire défiler l'écran. Cela améliore les performances de rendu et de défilement de la liste.
![Fenêtre de contenu dans une liste virtualisée](https://web.dev/static/articles/virtualize-long-lists-react-window/image/window-content-a-virtua-9c3c7f0cbf9a5.jpg?authuser=6&hl=fr)
Les nœuds DOM qui quittent la "fenêtre" sont recyclés ou sont immédiatement remplacés par des éléments plus récents à mesure que l'utilisateur fait défiler la liste. Cela permet de conserver le nombre d'éléments affichés spécifiques à la taille de la fenêtre.
fenêtre de réaction
react-window
est une petite bibliothèque tierce qui facilite la création de listes virtualisées dans votre application. Elle fournit un certain nombre d'API de base pouvant être utilisées pour différents types de listes et de tables.
Quand utiliser des listes de taille fixe ?
Utilisez le composant FixedSizeList
si vous avez une longue liste unidimensionnelle d'éléments de taille égale.
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;
- Le composant
FixedSizeList
accepte une propriétéheight
,width
etitemSize
pour contrôler la taille des éléments de la liste. - Une fonction qui affiche les lignes est transmise en tant qu'enfant à
FixedSizeList
. Vous pouvez accéder aux détails de l'élément particulier avec l'argumentindex
(items[index]
). - Un paramètre
style
est également transmis à la méthode de rendu des lignes, qui doit être associé à l'élément de ligne. Les éléments de liste sont positionnés de manière absolue avec des valeurs de hauteur et de largeur intégrées de manière intégrée, et le paramètrestyle
en est responsable.
L'exemple de Glitch présenté précédemment dans cet article montre un exemple de composant FixedSizeList
.
Quand utiliser des listes de taille variable ?
Utilisez le composant VariableSizeList
pour afficher une liste d'éléments de différentes tailles. Ce composant fonctionne de la même manière qu'une liste de taille fixe, mais attend une fonction pour la propriété itemSize
au lieu d'une valeur spécifique.
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;
L'intégration suivante montre un exemple de ce composant.
La fonction de taille de l'élément transmise à la propriété itemSize
attribue de manière aléatoire la hauteur des lignes dans cet exemple. Toutefois, dans une application réelle, il doit y avoir une logique réelle définissant les tailles de chaque élément. Idéalement, ces tailles doivent être calculées à partir de données ou obtenues à partir d'une API.
Grilles
react-window
prend également en charge la virtualisation des listes ou grilles multidimensionnelles. Dans ce contexte, la "fenêtre" de contenu visible change lorsque l'utilisateur fait défiler l'écran horizontalement et verticalement.
![Le déplacement d'une fenêtre de contenu dans une grille virtualisée est bidimensionnel](https://web.dev/static/articles/virtualize-long-lists-react-window/image/moving-window-content-a-3393c2a96ff59.jpg?authuser=6&hl=fr)
De même, les composants FixedSizeGrid
et VariableSizeGrid
peuvent être utilisés selon que la taille d'éléments de liste spécifiques peut varier ou non.
- Pour
FixedSizeGrid
, l'API est à peu près la même, mais avec le fait que les hauteurs, la largeur et le nombre d'éléments doivent être représentés à la fois pour les colonnes et les lignes. - Pour
VariableSizeGrid
, il est possible de modifier la largeur des colonnes et la hauteur des lignes en transmettant des fonctions au lieu de valeurs à leurs propriétés respectives.
Consultez la documentation pour voir des exemples de grilles virtualisées.
Chargement différé lors du défilement
De nombreux sites Web améliorent les performances en attendant que l'utilisateur ait fait défiler la page vers le bas pour charger et afficher les éléments d'une longue liste. Cette technique, communément appelée "chargement infini", ajoute de nouveaux nœuds DOM à la liste lorsque l'utilisateur fait défiler la page au-delà d'un certain seuil. Bien que cette méthode soit préférable au chargement simultané de tous les éléments d'une liste, elle finit par remplir le DOM avec des milliers d'entrées de ligne si l'utilisateur en a fait défiler le nombre. Cela peut entraîner une taille de DOM trop importante, qui commence à avoir un impact sur les performances en ralentissant les calculs de style et les mutations DOM.
Le diagramme suivant peut vous aider à résumer cela:
![Différence de défilement entre une liste régulière et une liste virtualisée](https://web.dev/static/articles/virtualize-long-lists-react-window/image/difference-scrolling-bet-517c003019905.jpg?authuser=6&hl=fr)
La meilleure approche pour résoudre ce problème consiste à continuer à utiliser une bibliothèque telle que react-window
pour conserver une petite "fenêtre" d'éléments sur une page, mais également pour charger les entrées les plus récentes de manière différée lorsque l'utilisateur fait défiler la page vers le bas. Un package distinct, react-window-infinite-loader
, rend cela possible avec react-window
.
Prenons l'exemple de code suivant, qui montre un exemple d'état géré dans un composant App
parent.
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;
Une méthode loadMore
est transmise à un ListComponent
enfant contenant la liste infinie des chargeurs. Ce point est important, car le chargeur infini doit déclencher un rappel pour charger d'autres éléments une fois que l'utilisateur a fait défiler la page au-delà d'un certain point.
Voici à quoi peut ressembler le ListComponent
qui affiche la liste:
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;
Ici, le composant FixedSizeList
est encapsulé dans InfiniteLoader
.
Les accessoires attribués au chargeur sont les suivants:
isItemLoaded
: méthode qui vérifie si un certain élément a été chargéitemCount
: nombre d'éléments dans la liste (ou attendu)loadMoreItems
: rappel qui renvoie une promesse qui se résout en données supplémentaires pour la liste.
Une propagation de rendu permet de renvoyer une fonction utilisée par le composant de liste pour effectuer le rendu.
Les attributs onItemsRendered
et ref
sont tous deux des attributs qui doivent être transmis.
L'exemple suivant montre comment le chargement infini peut fonctionner avec une liste virtualisée.
Faire défiler la liste peut sembler identique, mais une requête est désormais envoyée pour récupérer 10 utilisateurs à partir d'une API d'utilisateur aléatoire chaque fois que vous faites défiler la liste vers la fin. Tout cela, tout en n'affichant qu'une seule "fenêtre" de résultats à la fois.
La vérification de l'index
d'un élément donné permet d'afficher un état de chargement différent selon qu'une requête a été effectuée pour des entrées plus récentes et que l'élément est toujours en cours de chargement.
Exemple :
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Surbalayage
Étant donné que les éléments d'une liste virtualisée changent uniquement lorsque l'utilisateur fait défiler l'écran, un espace vide peut clignoter brièvement lorsque des entrées plus récentes sont sur le point d'être affichées. Vous pouvez essayer de faire défiler rapidement l'un des exemples précédents de ce guide pour vous en apercevoir.
Pour améliorer l'expérience utilisateur des listes virtualisées, react-window
vous permet de suranalyser les éléments avec la propriété overscanCount
. Cela vous permet de définir le nombre d'éléments à afficher en permanence en dehors de la "fenêtre" visible.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
fonctionne à la fois pour les composants FixedSizeList
et VariableSizeList
, et a une valeur par défaut de 1. En fonction de la taille d'une liste et de la taille de chaque élément, suranalyser plusieurs entrées peut aider à éviter un flash visible d'espace vide lorsque l'utilisateur fait défiler la page. Toutefois, un suranalyse d'un trop grand nombre d'entrées peut avoir un impact négatif sur les performances. L'utilisation d'une liste virtualisée vise à réduire le plus possible le nombre d'entrées visibles par l'utilisateur à un moment donné. Par conséquent, essayez de réduire le plus possible le nombre d'éléments suranalysés.
Pour FixedSizeGrid
et VariableSizeGrid
, utilisez les propriétés overscanColumnsCount
et overscanRowsCount
pour contrôler le nombre de colonnes et de lignes à suranalyser, respectivement.
Conclusion
Si vous ne savez pas par où commencer pour virtualiser les listes et les tables dans votre application, procédez comme suit:
- Mesurez les performances de rendu et de défilement. Cet article explique comment utiliser le mesure de FPS des outils pour les développeurs Chrome afin de déterminer l'efficacité de l'affichage des éléments dans une liste.
- Incluez
react-window
pour toutes les longues listes ou grilles qui affectent les performances. - Si certaines fonctionnalités ne sont pas compatibles avec
react-window
, envisagez d'utiliserreact-virtualized
si vous ne pouvez pas les ajouter vous-même. - Encapsulez votre liste virtualisée avec
react-window-infinite-loader
si vous devez charger des éléments de manière différée lorsque l'utilisateur fait défiler la page. - Utilisez la propriété
overscanCount
pour vos listes, et les propriétésoverscanColumnsCount
etoverscanRowsCount
pour vos grilles afin d'éviter l'apparition de contenu vide. Ne suranalysez pas trop d'entrées, car cela aurait une incidence négative sur les performances.