Sehr große Tabellen und Listen können die Leistung Ihrer Website erheblich beeinträchtigen. Virtualisierung kann helfen.
react-window ist eine Bibliothek, mit der sich große Listen effizient rendern lassen.
Welchen Nutzen bieten sie?
Es kann vorkommen, dass Sie eine große Tabelle oder Liste mit vielen Zeilen anzeigen müssen. Wenn Sie jedes Element in einer solchen Liste laden, kann sich das erheblich auf die Leistung auswirken.
Bei der Listenvirtualisierung oder dem „Fenstering“ wird nur das gerendert, was für den Nutzer sichtbar ist. Die Anzahl der Elemente, die zuerst gerendert werden, ist ein sehr kleiner Teil der gesamten Liste. Das „Fenster“ mit sichtbaren Inhalten bewegt sich , wenn der Nutzer weiter scrollt. Dadurch wird sowohl die Rendering- als auch die Scrollleistung der Liste verbessert.
DOM-Knoten, die das „Fenster“ verlassen, werden wiederverwendet oder sofort durch neuere Elemente ersetzt, wenn der Nutzer in der Liste nach unten scrollt. So bleibt die Anzahl aller gerenderten Elemente auf die Größe des Fensters beschränkt.
react-window
react-window ist eine kleine Bibliothek eines Drittanbieters, mit der sich virtualisierte Listen in Ihrer Anwendung einfacher erstellen lassen. Sie bietet eine Reihe von Basis-APIs, die für verschiedene Arten von Listen und Tabellen verwendet werden können.
Wann sollten Listen mit fester Größe verwendet werden?
Verwenden Sie die Komponente FixedSizeList, wenn Sie eine lange, eindimensionale Liste mit gleich großen Elementen haben.
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;
- Die Komponente
FixedSizeListakzeptiert die Attributeheight,widthunditemSize, um die Größe der Elemente in der Liste zu steuern. - Eine Funktion, die die Zeilen rendert, wird als untergeordnetes Element an
FixedSizeListübergeben. Details zum jeweiligen Element können mit demindexArgument (items[index]) aufgerufen werden. - Außerdem wird ein
style-Parameter an die Zeilenrendering-Methode übergeben, der an das Zeilenelement angehängt werden muss. Listenelemente werden absolut positioniert, wobei ihre Werte für Höhe und Breite inline zugewiesen werden. Der Parameterstyleist dafür verantwortlich.
Wann sollten Listen mit variabler Größe verwendet werden?
Verwenden Sie die Komponente VariableSizeList, um eine Liste von Elementen mit unterschiedlichen Größen zu rendern. Diese Komponente funktioniert genauso wie eine Liste mit fester Größe, erwartet aber eine Funktion für das Attribut itemSize anstelle eines bestimmten Werts.
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;
Die Funktion für die Elementgröße, die an das Attribut itemSize übergeben wird, randomisiert in diesem Beispiel die Zeilenhöhen. In einer echten Anwendung sollte jedoch eine tatsächliche Logik vorhanden sein, die die Größen der einzelnen Elemente definiert. Idealerweise sollten diese Größen anhand von Daten berechnet oder über eine API abgerufen werden.
Grids
react-window unterstützt auch die Virtualisierung mehrdimensionaler Listen oder Grids. In diesem Fall ändert sich das „Fenster“ mit sichtbaren Inhalten, wenn der Nutzer horizontal und vertikal scrollt.
Ebenso können je nachdem, ob die Größe bestimmter Listenelemente variieren kann, die Komponenten FixedSizeGrid und VariableSizeGrid verwendet werden.
- Bei
FixedSizeGridist die API ungefähr gleich, aber Höhen, Breiten und Elementanzahlen müssen sowohl für Spalten als auch für Zeilen dargestellt werden. - Bei
VariableSizeGridkönnen sowohl die Spaltenbreiten als auch die Zeilenhöhen geändert werden, indem Funktionen anstelle von Werten an die entsprechenden Attribute übergeben werden.
Beispiele für virtualisierte Grids finden Sie in der Dokumentation.
Lazy Loading beim Scrollen
Viele Websites verbessern die Leistung, indem sie mit dem Laden und Rendern von Elementen in einer langen Liste warten, bis der Nutzer nach unten gescrollt hat. Bei dieser Technik, die allgemein als „unendliches Laden“ bezeichnet wird, werden der Liste neue DOM-Knoten hinzugefügt, wenn der Nutzer einen bestimmten Schwellenwert in der Nähe des Endes überschreitet. Das ist zwar besser, als alle Elemente in einer Liste gleichzeitig zu laden, aber es führt trotzdem dazu, dass das DOM mit Tausenden von Zeileneinträgen gefüllt wird, wenn der Nutzer so viele Einträge überschritten hat. Dies kann zu einer übermäßig großen DOM-Größe führen, was sich auf die Leistung auswirkt, indem Stilberechnungen und DOM-Mutationen verlangsamt werden.
Das folgende Diagramm kann helfen, dies zusammenzufassen:
Der beste Ansatz, um dieses Problem zu lösen, besteht darin, weiterhin eine Bibliothek wie react-window zu verwenden, um ein kleines „Fenster“ mit Elementen auf einer Seite zu erhalten, aber auch neuere Einträge verzögert zu laden, wenn der Nutzer nach unten scrollt. Ein separates Paket,
react-window-infinite-loader, macht dies mit react-window möglich.
Betrachten Sie den folgenden Code, der ein Beispiel für den Status zeigt, der in einer übergeordneten App-Komponente verwaltet wird.
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;
Eine loadMore-Methode wird an eine untergeordnete ListComponent übergeben, die die Liste mit dem unendlichen Loader enthält. Das ist wichtig, weil der unendliche Loader einen Callback auslösen muss, um weitere Elemente zu laden, sobald der Nutzer einen bestimmten Punkt überschritten hat.
So kann die ListComponent aussehen, die die Liste rendert:
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;
Hier ist die Komponente FixedSizeList in InfiniteLoader eingebettet.
Die dem Loader zugewiesenen Attribute sind:
isItemLoaded: Methode, die prüft, ob ein bestimmtes Element geladen wurdeitemCount: Anzahl der Elemente in der Liste (oder erwartet)loadMoreItems: Callback, das ein Promise zurückgibt, das in zusätzliche Daten für die Liste aufgelöst wird
Ein
Render-Attribut
wird verwendet, um eine Funktion zurückzugeben, die die Listenkomponente zum Rendern verwendet.
Sowohl onItemsRendered als auch ref sind Attribute, die übergeben werden müssen.
Das Scrollen in der Liste fühlt sich möglicherweise gleich an, aber jedes Mal, wenn Sie in der Liste fast ganz nach unten scrollen, wird eine Anfrage gesendet, um 10 Nutzer von einer zufälligen Nutzer-API abzurufen. Das alles geschieht, während jeweils nur ein „Fenster“ mit Ergebnissen gerendert wird.
Wenn Sie den index eines bestimmten Elements prüfen, kann je nachdem, ob eine Anfrage für neuere Einträge gesendet wurde und das Element noch geladen wird, ein anderer Ladestatus für ein Element angezeigt werden.
Beispiel:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Overscanning
Da sich Elemente in einer virtualisierten Liste nur ändern, wenn der Nutzer scrollt, kann kurz ein leerer Bereich aufblitzen, wenn neuere Einträge angezeigt werden. Sie können in einem der vorherigen Beispiele in diesem Leitfaden schnell scrollen, um das zu sehen.
Um die Nutzerfreundlichkeit virtualisierter Listen zu verbessern, können Sie mit react-window Elemente mit dem Attribut overscanCount überscannen. So können Sie festlegen, wie viele Elemente außerhalb des sichtbaren „Fensters“ jederzeit gerendert werden sollen.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount funktioniert sowohl für die Komponenten FixedSizeList als auch VariableSizeList und hat den Standardwert 1. Je nachdem, wie groß eine Liste ist und wie groß die einzelnen Elemente sind, kann es hilfreich sein, mehr als nur einen Eintrag zu überscannen, um ein sichtbares Aufblitzen eines leeren Bereichs zu verhindern, wenn der Nutzer scrollt. Wenn Sie jedoch zu viele Einträge überscannen, kann sich das negativ auf die Leistung auswirken. Der Sinn der Verwendung einer virtualisierten Liste besteht darin, die Anzahl der Einträge auf das zu beschränken, was der Nutzer zu einem bestimmten Zeitpunkt sehen kann. Versuchen Sie daher, die Anzahl der überscannten Elemente so gering wie möglich zu halten.
Verwenden Sie für FixedSizeGrid und VariableSizeGrid die Attribute overscanColumnsCount und overscanRowsCount, um die Anzahl der Spalten bzw. Zeilen zu steuern, die überscannt werden sollen.
Fazit
Wenn Sie sich nicht sicher sind, wo Sie mit der Virtualisierung von Listen und Tabellen in Ihrer Anwendung beginnen sollen, gehen Sie so vor:
- Messen Sie die Rendering- und Scrollleistung. In diesem Artikel wird gezeigt, wie Sie mit dem FPS Messgerät in den Chrome-Entwicklertools herausfinden können, wie effizient Elemente in einer Liste gerendert werden.
- Fügen Sie
react-windowfür alle langen Listen oder Grids hinzu, die die Leistung beeinträchtigen. - Wenn bestimmte Funktionen in
react-windownicht unterstützt werden, können Siereact-virtualizedverwenden, wenn Sie diese Funktion nicht selbst hinzufügen können. - Umschließen Sie Ihre virtualisierte Liste mit
react-window-infinite-loader, wenn Sie Elemente verzögert laden müssen, während der Nutzer scrollt. - Verwenden Sie das Attribut
overscanCountfür Ihre Listen und die AttributeoverscanColumnsCountundoverscanRowsCountfür Ihre Grids, um ein Aufblitzen von leerem Inhalt zu verhindern. Überscannen Sie nicht zu viele Einträge, da sich das negativ auf die Leistung auswirkt.