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 przewinąć stronę jak najszybciej.
Dlaczego to jest przydatne?
Czasami może być konieczne wyświetlenie dużej tabeli lub listy zawierającej wiele wierszy. Wczytanie każdego elementu na takiej liście może znacząco wpłynąć na skuteczność.
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ęzły DOM, z których wychodzi „okno”, są poddawane recyklingowi lub natychmiast są zastępowane nowymi elementami, gdy użytkownik przewinie stronę w dół. Dzięki temu liczba wszystkich wyrenderowanych elementów 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
. Dostęp do szczegółowych informacji o konkretnym elemencie można uzyskać za pomocą argumentuindex
(items[index]
). - Do metody renderowania wierszy 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 jest realizowane przez parametrstyle
.
Wcześniej pokazany w tym artykule przykład zakłócenia 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 oczekuje funkcji dla argumentu itemSize
.
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 znajduje się przykład tego komponentu.
Funkcja rozmiaru produktu przekazana do atrybutu itemSize
w tym przykładzie losuje wysokości wierszy. W rzeczywistych zastosowaniach powinna jednak istnieć logika definiująca rozmiary każdego produktu. Najlepiej, gdyby te rozmiary były obliczane na podstawie danych lub pochodziły z 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
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
szerokość kolumn i wysokość wierszy można zmieniać przez przekazanie funkcji zamiast wartości do odpowiednich właściwości.
Aby zobaczyć przykłady wirtualizowanych sieci, zapoznaj się z dokumentacją.
Leniwe ładowanie podczas przewijania
Wiele witryn poprawia wydajność, oczekując na wczytanie i renderowanie elementów z długiej listy, dopóki użytkownik nie przewinie strony 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 podsumowanie tego diagramu:
Najlepszym sposobem na rozwiązanie tego problemu jest dalsze korzystanie z biblioteki takiej jak react-window
, aby zachować małe „okno” elementów na stronie, ale też stosować ładowanie opóźnione nowych wpisów, 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ę wczytywania. Jest to ważne, ponieważ po przekroczeniu przez użytkownika określonego punktu ładowania nieskończony ładowar potrzebuje wywołać funkcję zwrotną, aby wczytać więcej elementów.
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ść render 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.
Podczas sprawdzania pola index
określonego elementu można dla niego pokazać inny stan wczytywania w zależności od tego, czy wysłano żądanie dla nowszych pozycji, a element wciąż się ładuje.
Na przykład:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Nadmiarowość obrazu
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
. Pozwala to określić, ile elementów poza widocznym „oknem” ma zostać wyrenderowane w danym momencie.
<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 tego, jak duża jest lista oraz od rozmiaru każdego elementu, nadmiarowe skanowanie więcej niż 1 wpisu może pomóc uniknąć wyraźnego pojawienia się pustej przestrzeni przy przewijaniu przez użytkownika. Zbyt duża liczba wpisów może jednak negatywnie wpłynąć na wydajność. Celem korzystania z listy wirtualnej 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ą miernika klatek na sekundę w Narzędziach deweloperskich w Chrome można sprawdzić wydajność renderowania elementów 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
. - Używaj właściwości
overscanCount
w przypadku list oraz właściwościoverscanColumnsCount
ioverscanRowsCount
w przypadku siatek, aby uniknąć mrugania pustej treści. Nie skanuj zbyt wielu pozycji, ponieważ może to negatywnie wpłynąć na wydajność.