Wirtualizacja dużych list za pomocą okna reakcji

Bardzo duże tabele i listy mogą znacznie spowolnić działanie witryny. Wirtualizacja może Ci w tym pomóc.

react-window to biblioteka, która umożliwia wydajne renderowanie dużych list.

Oto przykład listy zawierającej 1000 wierszy renderowanych za pomocą funkcji react-window. Spróbuj przewijać jak najszybciej.

Dlaczego to jest przydatne?

Czasami może być konieczne wyświetlenie dużej tabeli lub listy zawierającej wiele wierszy. Wczytywanie każdego elementu z takiej listy może znacznie wpłynąć na wydajność.

Wirtualizacja list, czyli „okno”, to koncepcja renderowania tylko tego, co jest widoczne dla użytkownika. Liczba elementów renderowanych na początku to bardzo mały podzbiór całej listy, a „okno” widocznych treści przesuwa się, gdy użytkownik kontynuuje przewijanie. Dzięki temu poprawisz wydajność listy zarówno pod względem renderowania, jak i przewijania.

Okno treści na liście wirtualnej
Przenoszenie „okna” treści na wirtualnej liście

W miarę przewijania listy w dół przez użytkownika węzły DOM, które opuszczają „okno”, są ponownie używane lub natychmiast zastępowane nowszymi elementami. Dzięki temu liczba wszystkich elementów wyrenderowanych jest dostosowana do rozmiaru okna.

react-window

react-window to niewielka biblioteka innej firmy, która ułatwia tworzenie w aplikacji wirtualnych list. Udostępnia ona kilka podstawowych interfejsów API, które można stosować w przypadku różnych typów list i tabel.

Kiedy używać list o stałym rozmiarze

Jeśli masz długą, jednowymiarową listę elementów o takim samym rozmiarze, użyj komponentu FixedSizeList.

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;
  • Komponent FixedSizeList przyjmuje właściwości height, widthitemSize, aby kontrolować rozmiar elementów na liście.
  • Funkcja, która renderuje wiersze, jest przekazywana jako element podrzędny do funkcji FixedSizeList. Szczegóły dotyczące konkretnego elementu można uzyskać za pomocą argumentu index (items[index]).
  • Do metody renderowania wiersza jest też przekazywany parametr style, który musi być dołączony do elementu wiersza. Elementy listy są pozycjonowane bezwzględnie, a ich wartości wysokości i szerokości są przypisane w miejscu, co zapewnia parametr style.

Przykład Glitcha pokazany wcześniej w tym artykule to przykład komponentu FixedSizeList.

Kiedy używać list o zmiennym rozmiarze

Użyj komponentu VariableSizeList, aby wyświetlić listę produktów o różnych rozmiarach. Ten komponent działa tak samo jak lista o stałym rozmiarze, ale zamiast konkretnej wartości w argumencie itemSize oczekuje funkcji.

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;

Poniżej znajdziesz przykład tego komponentu.

W tym przykładzie funkcja rozmiaru produktu przekazana do atrybutu itemSize losuje wysokości wierszy. W rzeczywistej aplikacji powinna jednak istnieć logika określająca rozmiary poszczególnych elementów. Najlepiej, aby te rozmiary były obliczane na podstawie danych lub uzyskiwane za pomocą interfejsu API.

Siatka

react-window obsługuje też wirtualizację wielowymiarowych list lub siatek. W tym kontekście „okno” widocznych treści zmienia się, gdy użytkownik przewija ekran poziomo i pionowo.

Przesuwanie okna z treścią w wirtualnej siatce jest dwuwymiarowe
Przesuwanie „okna” z treściami w wirtualnej siatce jest dwuwymiarowe

Podobnie możesz używać komponentów FixedSizeGridVariableSizeGrid w zależności od tego, czy rozmiar poszczególnych elementów listy może się różnić.

  • W przypadku atrybutu FixedSizeGrid interfejs API jest podobny, ale wysokość, szerokość i liczba elementów muszą być reprezentowane zarówno w kolumnach, jak i w wierszach.
  • W przypadku funkcji VariableSizeGrid można zmienić zarówno szerokość kolumn, jak i wysokość wierszy, przekazując do odpowiednich parametrów funkcje zamiast wartości.

Aby zobaczyć przykłady wirtualnych sieci, zapoznaj się z dokumentacją.

Leniwe ładowanie podczas przewijania

Wiele witryn poprawia wydajność, opóźniając wczytywanie i renderowanie elementów na długiej liście do momentu, gdy użytkownik przewinie stronę w dół. Ta technika, powszechnie określana jako „nieskończone wczytywanie”, dodaje do listy nowe węzły DOM, gdy użytkownik przewija stronę poniżej określonego progu w pobliżu końca. Chociaż jest to lepsze niż wczytywanie wszystkich elementów na liście naraz, to nadal wypełnia DOM tysiącami wierszy, jeśli użytkownik przewinął tak wiele wierszy. Może to prowadzić do nadmiernego zwiększenia rozmiaru DOM, co zaczyna wpływać na wydajność, ponieważ spowalnia obliczenia stylów i mutacje DOM.

Oto diagram podsumowujący tę kwestię:

Różnica w przewijaniu między zwykłą a wirtualną listą
różnica w przewijaniu między zwykłą a wirtualną listą

Najlepszym sposobem na rozwiązanie tego problemu jest dalsze korzystanie z biblioteki takiej jak react-window, aby zachować niewielkie „okno” elementów na stronie, ale też ładować nowsze wpisy w tylu, gdy użytkownik przewija stronę w dół. Możliwe jest to dzięki osobnemu pakietowi react-window-infinite-loader, który jest dostępny w ramach react-window.

Zastanów się nad tym fragmentem kodu, który pokazuje stan zarządzany w rodzicielskim komponencie App.

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;

Metoda loadMore jest przekazywana do podrzędnego elementu ListComponent, który zawiera nieskończoną listę ładowania. Jest to ważne, ponieważ po przekroczeniu przez użytkownika określonego punktu ładowania nieskończony ładowar potrzebuje wywołać funkcję zwrotną, aby wczytać kolejne elementy.

Oto jak może wyglądać element ListComponent, który renderuje listę:

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;

Tutaj komponent FixedSizeList jest ujęty w ramy komponentu InfiniteLoader. Elementy przypisane do ładowarki:

  • isItemLoaded: metoda sprawdzająca, czy określony element został załadowany
  • itemCount: liczba elementów na liście (lub oczekiwana)
  • loadMoreItems: funkcja wywołania zwracająca obietnicę, która zwraca dodatkowe dane dotyczące listy.

Właściwość renderowania służy do zwracania funkcji, której komponent listy używa do renderowania. Atrybuty onItemsRenderedref to atrybuty, które muszą zostać przekazane.

Poniżej znajdziesz przykład działania nieskończonego wczytywania z wirtualizowaną listą.

Przewijanie listy może wyglądać tak samo, ale za każdym razem, gdy przewiniesz w dół listy, wysyłane jest żądanie pobrania 10 użytkowników z interfejsu API losowego użytkownika. Wszystko to odbywa się podczas renderowania tylko jednego „okna” wyników naraz.

Gdy sprawdzasz index określonego elementu, możesz zobaczyć inny stan wczytywania w zależności od tego, czy żądanie zostało wysłane do nowszych wpisów i czy element jest nadal wczytywany.

Na przykład:

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

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

Overscanning

Elementy na wirtualnej liście zmieniają się tylko wtedy, gdy użytkownik przewija ekran, więc puste miejsce może się na chwilę wyświetlić, gdy mają się pojawić nowsze wpisy. Aby to zauważyć, możesz szybko przewinąć dowolny z poprzednich przykładów w tym przewodniku.

Aby zwiększyć wygodę użytkowników korzystających z list wirtualnych, funkcja react-window umożliwia skanowanie elementów z właściwością overscanCount. Dzięki temu możesz określić, ile elementów poza widocznym „oknem” ma być renderowanych przez cały czas.

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

Parametr overscanCount działa zarówno w przypadku komponentów FixedSizeList, jak i VariableSizeList. Jego domyślna wartość to 1. W zależności od długości listy oraz rozmiaru poszczególnych elementów skanowanie więcej niż 1 elementu może pomóc w uniknięciu wyświetlania pustego miejsca podczas przewijania przez użytkownika. Jednak przeskanowanie zbyt dużej liczby wpisów może negatywnie wpłynąć na wydajność. Celem korzystania z wirtualnej listy jest zminimalizowanie liczby pozycji do tych, które użytkownik może zobaczyć w danym momencie, dlatego staraj się, aby liczba elementów skanowanych nadmiernie była jak najmniejsza.

W przypadku FixedSizeGridVariableSizeGrid użyj właściwości overscanColumnsCountoverscanRowsCount, aby kontrolować odpowiednio liczbę kolumn i wierszy do przeskanowania.

Podsumowanie

Jeśli nie wiesz, od czego zacząć wirtualizację list i tablic w aplikacji, wykonaj te czynności:

  1. Pomiar wydajności podczas renderowania i przewijania. Z tego artykułu dowiesz się, jak za pomocą licznika FPS w Narzędziach dla programistów Chrome sprawdzić, jak wydajnie renderowane są elementy na liście.
  2. Uwzględnij react-window w przypadku długich list lub siatek, które wpływają na wydajność.
  3. Jeśli niektóre funkcje nie są obsługiwane w react-window, rozważ użycie react-virtualized, jeśli nie możesz samodzielnie dodać tej funkcji.
  4. Jeśli chcesz stosować opóźnione wczytywanie elementów podczas przewijania przez użytkownika, zwróć uwagę, aby kończyć wirtualizowaną listę elementem react-window-infinite-loader.
  5. Aby zapobiec wyświetlaniu pustych treści, użyj właściwości overscanCount w przypadku list oraz właściwości overscanColumnsCountoverscanRowsCount w przypadku siatek. Nie skanuj zbyt wielu pozycji, ponieważ może to negatywnie wpłynąć na wydajność.