Bardzo duże tabele i listy mogą znacznie spowalniać działanie witryny. Może pomóc wirtualizacja.
react-window
to biblioteka, która umożliwia efektywne renderowanie dużych list.
Oto przykład listy zawierającej 1000 wierszy renderowanych za pomocą funkcji react-window
. Spróbuj przewinąć stronę tak szybko, jak to możliwe.
Dlaczego to takie przydatne?
Może się zdarzyć, że musisz wyświetlić dużą tabelę lub listę zawierającą wiele wierszy. Wczytywanie każdego elementu na takiej liście może znacząco wpłynąć na wydajność.
Wirtualizacja listy lub „okno” oznacza renderowanie tylko tego, co jest widoczne dla użytkownika. Liczba elementów renderowanych na początku stanowi bardzo mały podzbiór całej listy, a „okno” widocznej zawartości przesuwa się, gdy użytkownik kontynuuje przewijanie. Poprawia to zarówno wydajność renderowania, jak i przewijania listy.
Węzły DOM, które zamykają „okno”, są odnawiane lub natychmiast zastępowane nowszymi elementami, gdy użytkownik przewija listę. Dzięki temu liczba wszystkich renderowanych elementów będzie dostosowana do rozmiaru okna.
okno reakcji
react-window
to mała biblioteka innej firmy, która ułatwia tworzenie wirtualnych list w aplikacji. Udostępnia on szereg podstawowych interfejsów API,
których można używać z różnymi rodzajami list i tabel.
Kiedy używać list o stałym rozmiarze
Jeśli masz długą, jednowymiarową listę elementów o jednakowych rozmiarach, 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
akceptuje właściwościheight
,width
iitemSize
, aby kontrolować rozmiar elementów na liście. - Funkcja, która renderuje wiersze, jest przekazywana do funkcji
FixedSizeList
jako element podrzędny. Aby uzyskać dostęp do informacji o konkretnym elemencie, użyj argumentuindex
(items[index]
). - Parametr
style
jest też przekazywany do metody renderowania wierszy, która musi być dołączony do elementu wiersza. Elementy list są umieszczone bezwzględnie, a ich wartości wysokości i szerokości są przypisane w tekście, za co odpowiada parametrstyle
.
We wcześniejszym artykule o tym artykule znajdziesz przykład komponentu FixedSizeList
.
Kiedy używać list o zmiennej rozmiaru
Aby wygenerować listę elementów o różnych rozmiarach, użyj komponentu VariableSizeList
. Ten komponent działa tak samo jak lista o stałym rozmiarze, ale oczekuje funkcji właściwości itemSize
zamiast konkretnej wartości.
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;
Umieszczony komponent to przykładowy element.
Funkcja rozmiaru elementu przekazywana do funkcji itemSize
losowo określa wysokość wierszy w tym przykładzie. Jednak w praktyce powinna istnieć logika definiująca rozmiar każdego elementu. W miarę możliwości rozmiary powinny być obliczane na podstawie
danych lub pobierane z interfejsu API.
Siatki
react-window
obsługuje też wirtualizację wielowymiarowych list (siatek). W tym kontekście „okno” widocznej treści zmienia się, gdy użytkownik przewija stronę w poziomie i w pionie.
Podobnie można używać komponentów FixedSizeGrid
i VariableSizeGrid
w zależności od tego, czy rozmiary elementów listy mogą się różnić.
- W przypadku
FixedSizeGrid
interfejs API jest mniej więcej taki sam, ale w obu kolumnach i wierszach trzeba podać wysokość, szerokość i liczbę elementów. - W przypadku funkcji
VariableSizeGrid
szerokość kolumn i wysokość wierszy można zmienić, przekazując do nich funkcje zamiast wartości.
Przykłady zwirtualizowanych siatek znajdziesz w dokumentacji.
Leniwe ładowanie podczas przewijania
Wiele witryn podnosi wydajność, czekając na wczytanie i renderowanie elementów na długiej liście, aż użytkownik przewinie stronę w dół. Ta technika, często określana jako „nieskończone ładowanie”, dodaje do listy nowe węzły DOM, gdy użytkownik przewija stronę blisko określonego progu. To lepsze rozwiązanie niż ładowanie wszystkich elementów z listy od razu, ale w przypadku przewinięcia bloka DOM umieszcza w tym przypadku tysiące wierszy. Może to prowadzić do zbyt dużego rozmiaru DOM, który zaczyna wpływać na wydajność przez spowalnianie obliczeń stylu i mutacji DOM.
Ten diagram może pomóc w podsumowaniu informacji:
Najlepszym sposobem rozwiązania tego problemu jest dalsze korzystanie z biblioteki takiej jak react-window
, aby zachować małe „okno” elementów na stronie i leniwe ładowanie nowszych wpisów, gdy użytkownik przewija stronę w dół. Oddzielny pakiet,
react-window-infinite-loader
, umożliwia to react-window
.
Przyjrzyjmy się fragmentowi kodu poniżej, który pokazuje przykład stanu zarządzanego w nadrzędnym 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 listę nieskończonych modułów ładowania. Jest to ważne, ponieważ nieskończone wczytywanie musi uruchomić wywołanie zwrotne, aby wczytać więcej elementów, gdy użytkownik przewinie widok poza określony punkt.
Przykładowa funkcja ListComponent
, która renderuje listę, może wyglądać tak:
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;
W tym przypadku komponent FixedSizeList
jest zawarty w komponencie InfiniteLoader
.
Obiekty przypisane do ładowania:
isItemLoaded
: metoda sprawdzania, czy określony element został wczytany.itemCount
: liczba produktów na liście (lub oczekiwanej).loadMoreItems
: wywołanie zwrotne, które zwraca obietnicę, która prowadzi do dodatkowych danych z listy.
Prop. renderowania służy do zwrócenia funkcji, której komponent listy używa do renderowania.
Przekazujemy zarówno atrybuty onItemsRendered
, jak i ref
.
Poniżej znajdziesz przykład działania nieskończonego wczytywania listy z wirtualizacją.
Przewijanie listy może wydawać się podobne, ale za każdym razem, gdy przewijasz do końca listy, następuje żądanie pobrania 10 użytkowników z interfejsu API losowego użytkownika. Wszystko to dzieje się podczas renderowania tylko jednego „okna” wyników naraz.
Podczas sprawdzania index
danego elementu może pokazać się inny stan wczytywania elementu w zależności od tego, czy wysłano żądanie dotyczące nowszych wpisów, a element nadal się wczytuje.
Na przykład:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Nadmiarowe skanowanie
Elementy na zwirtualizowanej liście zmieniają się tylko wtedy, gdy użytkownik przewija stronę, dlatego puste miejsce może na chwilę migać, gdy zbliżają się nowe pozycje. Aby to zauważyć, możesz szybko przewinąć dowolny z poprzednich przykładów w tym przewodniku.
Aby zwiększyć wygodę użytkowników zwirtualizowanych list, react-window
umożliwia nadmiarowe skanowanie elementów za pomocą właściwości overscanCount
. Pozwala to określić, ile elementów poza widocznym „oknem” ma być stale renderowane.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
działa zarówno w przypadku komponentów FixedSizeList
, jak i VariableSizeList
i ma wartość domyślną 1. W zależności od tego, jak duża jest lista, a także od rozmiaru każdego elementu, nadmierne skanowanie więcej niż 1 wpisu może zapobiec zauważalnemu błyskowi pustej przestrzeni podczas przewijania. Jednak nadmierne skanowanie zbyt wielu wpisów może negatywnie wpłynąć na wydajność. Korzystanie z wirtualizowanej listy polega na zminimalizowaniu liczby wpisów do poziomu, który użytkownik może zobaczyć w danej chwili. Dlatego staraj się więc ograniczyć liczbę przeskanowanych elementów do jak najmniejszej liczby.
W przypadku FixedSizeGrid
i VariableSizeGrid
użyj właściwości overscanColumnsCount
i overscanRowsCount
, aby określić liczbę kolumn i wierszy, które mają być nadmiarowe.
Podsumowanie
Jeśli nie masz pewności, od czego zacząć wirtualizację list i tabel w aplikacji, wykonaj te czynności:
- Mierz wydajność renderowania i przewijania. Z tego artykułu dowiesz się, jak za pomocą miernika klatek na sekundę dostępnego w Narzędziach deweloperskich w Chrome można sprawdzić wydajność renderowania elementów na liście.
- W przypadku długich list lub siatki, które wpływają na wydajność, uwzględnij atrybut
react-window
. - Jeśli nie jesteś w stanie samodzielnie dodać niektórych funkcji do
react-window
, rozważ użyciereact-virtualized
. - Jeśli potrzebujesz leniwego ładowania elementów, gdy użytkownik przewija stronę, dodaj do zwirtualizowanej listy element
react-window-infinite-loader
. - Używaj w przypadku list właściwości
overscanCount
, a w siatkach – właściwościoverscanColumnsCount
ioverscanRowsCount
, aby uniknąć błysku pustej zawartości. Nie skanuj zbyt wielu wpisów, ponieważ wpłynie to negatywnie na wydajność.