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.
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ściheight
,width
iitemSize
, 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ą argumentuindex
(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 parametrstyle
.
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.
Podobnie możesz używać komponentów FixedSizeGrid
i VariableSizeGrid
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ę:
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ładowanyitemCount
: 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 onItemsRendered
i ref
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 FixedSizeGrid
i VariableSizeGrid
użyj właściwości overscanColumnsCount
i overscanRowsCount
, 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:
- 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.
- Uwzględnij
react-window
w przypadku długich list lub siatek, które wpływają na wydajność. - Jeśli niektóre funkcje nie są obsługiwane w
react-window
, rozważ użyciereact-virtualized
, jeśli nie możesz samodzielnie dodać tej funkcji. - 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
. - Aby zapobiec wyświetlaniu pustych treści, użyj właściwości
overscanCount
w przypadku list oraz właściwościoverscanColumnsCount
ioverscanRowsCount
w przypadku siatek. Nie skanuj zbyt wielu pozycji, ponieważ może to negatywnie wpłynąć na wydajność.