Große Listen mit dem Reaktionsfenster virtualisieren

Sehr große Tabellen und Listen können die Leistung Ihrer Website erheblich beeinträchtigen. Hier kann Virtualisierung helfen.

react-window ist eine Bibliothek, mit der große Listen effizient gerendert werden können.

Hier ein Beispiel für eine Liste mit 1.000 Zeilen, die mit react-window gerendert wird. Scrollen Sie so schnell wie möglich.

Welchen Nutzen bieten sie?

Manchmal müssen Sie eine große Tabelle oder Liste mit vielen Zeilen anzeigen. Das Laden jedes einzelnen Elements in einer solchen Liste kann sich erheblich auf die Leistung auswirken.

Bei der Listenvirtualisierung oder „Windowing“ 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 den sichtbaren Inhalten verschiebt sich, wenn der Nutzer weiter scrollt. Dadurch wird sowohl die Rendering- als auch die Scrollleistung der Liste verbessert.

Inhaltsbereich in einer virtuellen Liste
Inhalte in einer virtuellen Liste verschieben

DOM-Knoten, die das „Fenster“ verlassen, werden wiederverwendet oder sofort durch neue Elemente ersetzt, wenn der Nutzer in der Liste nach unten scrollt. So bleibt die Anzahl aller gerenderten Elemente auf die Größe des Fensters abgestimmt.

react-window

react-window ist eine kleine Drittanbieterbibliothek, mit der sich virtuelle 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 FixedSizeList-Komponente akzeptiert die Eigenschaften height, width und itemSize, 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 Artikel können mit dem Argument index (items[index]) abgerufen werden.
  • Außerdem wird der Parameter style an die Zeile-Rendering-Methode übergeben, die dem Zeilenelement hinzugefügt werden muss. Listenelemente sind absolut positioniert und ihre Werte für Höhe und Breite werden inline zugewiesen. Dafür ist der Parameter style verantwortlich.

Das Glitch-Beispiel, das weiter oben in diesem Artikel gezeigt wurde, ist ein Beispiel für eine FixedSizeList-Komponente.

Wann sollten Listen mit variabler Größe verwendet werden?

Mit der Komponente VariableSizeList können Sie eine Liste von Elementen mit unterschiedlichen Größen rendern. Diese Komponente funktioniert genauso wie eine Liste mit fester Größe, erwartet aber für die itemSize-Property eine Funktion 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;

Das folgende eingebettete Element zeigt ein Beispiel für diese Komponente.

Die Artikelgrößenfunktion, die an das itemSize-Attribut übergeben wird, gibt in diesem Beispiel zufällige Zeilenhöhen aus. In einer echten Anwendung sollte jedoch eine Logik vorhanden sein, die die Größe der einzelnen Elemente definiert. Idealerweise sollten diese Größen anhand von Daten berechnet oder aus einer API abgerufen werden.

Raster

react-window unterstützt auch die Virtualisierung mehrdimensionaler Listen oder Raster. In diesem Zusammenhang ändert sich das „Fenster“ der sichtbaren Inhalte, wenn der Nutzer horizontal und vertikal scrollt.

Das bewegliche Fenster mit Inhalten in einem virtuellen Raster ist zweidimensional
Das Verschieben des Inhaltsfensters in einem virtualisierten Raster ist zweidimensional.

Ebenso können sowohl FixedSizeGrid- als auch VariableSizeGrid-Komponenten verwendet werden, je nachdem, ob die Größe bestimmter Listenelemente variieren kann.

  • Für FixedSizeGrid ist die API ungefähr gleich, aber Höhe, Breite und Artikelanzahl müssen sowohl für Spalten als auch für Zeilen angegeben werden.
  • Bei VariableSizeGrid können sowohl die Spaltenbreiten als auch die Zeilenhöhen geändert werden, indem den entsprechenden Props Funktionen statt Werte übergeben werden.

In der Dokumentation finden Sie Beispiele für virtualisierte Raster.

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 diesem Verfahren, das allgemein als „endloses Laden“ bezeichnet wird, werden der Liste neue DOM-Knoten hinzugefügt, wenn der Nutzer einen bestimmten Grenzwert nahe dem Ende überschreitet. Das ist zwar besser als das gleichzeitige Laden aller Elemente in einer Liste, aber das DOM wird trotzdem mit Tausenden von Zeileneinträgen gefüllt, wenn der Nutzer so weit gescrollt hat. Dies kann zu einer übermäßig großen DOM-Größe führen, was sich auf die Leistung auswirkt, da Stilberechnungen und DOM-Mutationen verlangsamt werden.

Das folgende Diagramm veranschaulicht dies:

Unterschied beim Scrollen zwischen einer normalen und einer virtuellen Liste
Unterschied beim Scrollen zwischen einer normalen und einer virtualisierten Liste

Die beste Lösung für dieses Problem 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 beim Scrollen des Nutzers träge zu laden. Mit dem separaten Paket react-window-infinite-loader ist dies mit react-window möglich.

Im folgenden Codebeispiel wird ein Beispiel für einen Status gezeigt, 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 ein untergeordnetes ListComponent übergeben, das die Liste der unendlichen Lader enthält. Das ist wichtig, weil der Endlos-Lademechanismus einen Rückruf auslösen muss, um weitere Elemente zu laden, sobald der Nutzer einen bestimmten Punkt passiert hat.

So könnte das ListComponent aussehen, das 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 FixedSizeList-Komponente in InfiniteLoader verschachtelt. Dem Lader sind folgende Props zugewiesen:

  • isItemLoaded: Methode, mit der geprüft wird, ob ein bestimmter Artikel geladen wurde
  • itemCount: Anzahl der Elemente in der Liste (oder erwartete Anzahl)
  • loadMoreItems: Callback, der ein Versprechen zurückgibt, das zu zusätzlichen Daten für die Liste führt

Mit einer Render-Prop wird eine Funktion zurückgegeben, die die Listenkomponente zum Rendern verwendet. Sowohl das onItemsRendered- als auch das ref-Attribut müssen übergeben werden.

Im folgenden Beispiel wird gezeigt, wie das endlose Laden mit einer virtuellen Liste funktioniert.

Das Scrollen durch die Liste fühlt sich möglicherweise gleich an, aber jedes Mal, wenn Sie fast am Ende der Liste angelangt sind, wird eine Anfrage gesendet, um 10 Nutzer aus einer Zufallsnutzer-API abzurufen. Dabei wird immer nur ein einziges „Fenster“ mit Ergebnissen gerendert.

Wenn Sie die index eines bestimmten Elements prüfen, kann für das Element ein anderer Ladestatus angezeigt werden, je nachdem, ob eine Anfrage für neuere Einträge gestellt wurde und das Element noch geladen wird.

Beispiel:

const Row = ({ index, style }) => {
 
const itemLoading = index === items.length;

 
if (itemLoading) {
     
// return loading state
 
} else {
     
// return item
 
}
};

Overscan

Da sich die Elemente in einer virtuellen Liste nur ändern, wenn der Nutzer scrollt, kann kurz ein leerer Bereich aufblinken, wenn neuere Einträge angezeigt werden sollen. Sie können das selbst ausprobieren, indem Sie schnell durch die vorherigen Beispiele in diesem Leitfaden scrollen.

Zur Verbesserung der Nutzerfreundlichkeit von virtuellen Listen können Sie mit react-window Elemente mit der Property overscanCount überscannen. So können Sie festlegen, wie viele Elemente außerhalb des sichtbaren „Fensters“ immer gerendert werden sollen.

<FixedSizeList
 
//...
 
overscanCount={4}
>
  {...}
</FixedSizeList>

overscanCount funktioniert sowohl für die FixedSizeList- als auch für die VariableSizeList-Komponente und hat den Standardwert 1. Je nachdem, wie groß eine Liste ist und wie groß die einzelnen Elemente sind, kann das Überspringen mehrerer Einträge dazu beitragen, ein unauffälliges Aufblitzen von leeren Bereichen beim Scrollen des Nutzers zu verhindern. Wenn jedoch zu viele Einträge gescannt werden, kann sich das negativ auf die Leistung auswirken. Der Sinn einer virtuellen Liste besteht darin, die Anzahl der Einträge auf die zu reduzieren, die der Nutzer zu einem bestimmten Zeitpunkt sehen kann. Versuchen Sie daher, die Anzahl der überflogenen Elemente so gering wie möglich zu halten.

Verwenden Sie für FixedSizeGrid und VariableSizeGrid die Properties overscanColumnsCount und overscanRowsCount, um die Anzahl der zu überscannenden Spalten bzw. Zeilen zu steuern.

Fazit

Wenn Sie nicht sicher sind, wo Sie mit der Virtualisierung von Listen und Tabellen in Ihrer Anwendung beginnen sollen, gehen Sie so vor:

  1. Leistung beim Rendern und Scrollen messen In diesem Artikel wird gezeigt, wie Sie mit dem FPS-Messgerät in den Chrome DevTools ermitteln können, wie effizient Elemente in einer Liste gerendert werden.
  2. Fügen Sie react-window für lange Listen oder Raster ein, die sich auf die Leistung auswirken.
  3. Wenn bestimmte Funktionen in react-window nicht unterstützt werden, können Sie react-virtualized verwenden, wenn Sie diese Funktionen nicht selbst hinzufügen können.
  4. Wenn Sie Elemente beim Scrollen des Nutzers verzögert laden möchten, schließen Sie Ihre virtualisierte Liste mit react-window-infinite-loader ab.
  5. Verwenden Sie die Property overscanCount für Listen und die Properties overscanColumnsCount und overscanRowsCount für Raster, um das Aufblitzen leerer Inhalte zu verhindern. Scannen Sie nicht zu viele Einträge, da sich dies negativ auf die Leistung auswirken kann.