Bardzo duże tabele i listy mogą znacznie spowolnić działanie witryny. Wirtualizacja może pomóc.
react-window
to biblioteka, która umożliwia wydajne renderowanie dużych list.
Oto przykład listy zawierającej 1000 wierszy, która jest renderowana za pomocą elementu react-window
. Spróbuj przewijać jak najszybciej.
Dlaczego ta funkcja jest przydatna?
Czasami może być konieczne wyświetlenie dużej tabeli lub listy zawierającej wiele wierszy. Wczytywanie każdego elementu na takiej liście może znacznie wpłynąć na wydajność.
Wirtualizacja listy, czyli „okienkowanie”, to koncepcja renderowania tylko tego, co jest widoczne dla użytkownika. Liczba elementów renderowanych na początku jest bardzo małym podzbiorem całej listy, a „okno” widocznych treści przesuwa się, gdy użytkownik przewija dalej. Poprawia to zarówno renderowanie, jak i przewijanie listy.

Węzły DOM, które opuszczają „okno”, są ponownie wykorzystywane lub natychmiast zastępowane nowszymi elementami, gdy użytkownik przewija listę w dół. Dzięki temu liczba wszystkich renderowanych elementów jest dostosowana do rozmiaru okna.
react-window
react-window
to niewielka biblioteka innej firmy, która ułatwia tworzenie wirtualnych list w aplikacji. Udostępnia on wiele podstawowych interfejsów API, których można używać w przypadku różnych typów list i tabel.
Kiedy używać list o stałym rozmiarze
Użyj komponentu FixedSizeList
, jeśli masz długą, jednowymiarową listę elementów o jednakowych rozmiarach.
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
, które kontrolują rozmiar elementów na liście. - Funkcja renderująca wiersze jest przekazywana jako element podrzędny do komponentu
FixedSizeList
. Szczegóły dotyczące konkretnego elementu można uzyskać za pomocą argumentuindex
(items[index]
). - Do metody renderowania wiersza przekazywany jest też parametr
style
, który musi być dołączony do elementu wiersza. Elementy listy są pozycjonowane bezwzględnie, a wartości ich wysokości i szerokości są przypisywane w wierszu. Odpowiada za to parametrstyle
.
Przykład Glitch przedstawiony wcześniej w tym artykule pokazuje komponent FixedSizeList
.
Kiedy używać list o zmiennej wielkości
Użyj komponentu VariableSizeList
, aby wyrenderować 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 właściwości 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ższy kod do umieszczenia pokazuje przykład tego komponentu.
Funkcja rozmiaru elementu przekazana do właściwości itemSize
losuje w tym przykładzie wysokości wierszy. W prawdziwej aplikacji powinna jednak istnieć rzeczywista logika określająca rozmiary poszczególnych elementów. Najlepiej, aby te rozmiary były obliczane na podstawie danych lub uzyskiwane z interfejsu API.
Siatki
react-window
obsługuje też wirtualizację list wielowymiarowych lub siatek. W tym kontekście „okno” widocznych treści zmienia się, gdy użytkownik przewija je w poziomie i w pionie.

Podobnie można 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ści, szerokości i liczby elementów muszą być reprezentowane zarówno w przypadku kolumn, jak i wierszy. - W przypadku komponentu
VariableSizeGrid
można zmienić zarówno szerokość kolumn, jak i wysokość wierszy, przekazując funkcje zamiast wartości do odpowiednich właściwości.
Przykłady zwirtualizowanych siatek znajdziesz w dokumentacji.
Leniwe ładowanie podczas przewijania
Wiele witryn poprawia wydajność, czekając z wczytaniem i wyrenderowaniem elementów na długiej liście, aż użytkownik przewinie stronę w dół. Ta technika, powszechnie określana jako „nieskończone wczytywanie”, dodaje nowe węzły DOM do listy, gdy użytkownik przewija ją do określonego progu blisko końca. Chociaż jest to lepsze rozwiązanie niż wczytywanie wszystkich elementów na liście naraz, w przypadku przewinięcia przez użytkownika dużej liczby wierszy w DOM-ie nadal może się pojawić wiele tysięcy wpisów. Może to prowadzić do nadmiernie dużego rozmiaru DOM, co zaczyna wpływać na wydajność, ponieważ obliczenia stylów i mutacje DOM są wolniejsze.
Może Ci w tym pomóc poniższy diagram:

Najlepszym rozwiązaniem tego problemu jest dalsze korzystanie z biblioteki takiej jak react-window
, aby utrzymywać małe „okno” elementów na stronie, ale także leniwe wczytywanie nowszych wpisów podczas przewijania strony w dół. Umożliwia to osobny pakiet react-window-infinite-loader
, który zawiera react-window
.
Przyjrzyj się poniższemu fragmentowi kodu, który pokazuje przykład stanu zarządzanego w komponencie nadrzędnym 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;
Do komponentu podrzędnego ListComponent
, który zawiera listę nieskończonego ładowania, przekazywana jest loadMore
metoda. Jest to ważne, ponieważ po przewinięciu strony do określonego miejsca nieskończony moduł wczytujący musi 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;
W tym przypadku komponent FixedSizeList
jest zawarty w InfiniteLoader
.
Do modułu wczytującego przypisane są te właściwości:
isItemLoaded
: metoda sprawdzająca, czy dany element został wczytanyitemCount
: liczba elementów na liście (lub oczekiwana liczba elementów)loadMoreItems
: funkcja zwrotna, która zwraca obietnicę, która jest rozwiązywana w postaci dodatkowych danych dla listy.
Render prop
służy do zwracania funkcji, której komponent listy używa do renderowania.
Atrybuty onItemsRendered
i ref
muszą być przekazywane.
Poniżej znajdziesz przykład działania nieskończonego ładowania w przypadku listy wirtualizowanej.
Przewijanie listy może wyglądać tak samo, ale za każdym razem, gdy przewiniesz ją prawie do końca, wysyłane jest żądanie pobrania 10 użytkowników z interfejsu API losowych użytkowników. Wszystko to odbywa się przy jednoczesnym renderowaniu tylko jednego „okna” wyników.
Po kliknięciu index
przy określonym elemencie może się wyświetlić inny stan ładowania elementu w zależności od tego, czy wysłano prośbę o nowe wpisy i czy element nadal 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 liście wirtualizowanej zmieniają się tylko wtedy, gdy użytkownik przewija ekran, więc puste miejsce może na chwilę błysnąć, gdy mają się pojawić nowsze wpisy. Możesz to sprawdzić, szybko przewijając dowolny z poprzednich przykładów w tym przewodniku.
Aby zwiększyć wygodę użytkowników korzystających z list wirtualizowanych, react-window
umożliwia
przeszukiwanie elementów za pomocą właściwości overscanCount
. Dzięki temu możesz określić, ile elementów poza widocznym „oknem” ma być zawsze renderowanych.
<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 wielkości listy i rozmiaru poszczególnych elementów nadmierne skanowanie więcej niż 1 pozycji może zapobiec zauważalnemu błyskowi pustej przestrzeni podczas przewijania przez użytkownika. Jednak skanowanie zbyt wielu wpisów może negatywnie wpłynąć na wydajność. Cały sens korzystania z wirtualizowanej listy polega na zminimalizowaniu liczby pozycji do tych, które użytkownik może zobaczyć w danym momencie, więc staraj się, aby liczba elementów, które zostały przeskanowane, była jak najmniejsza.
W przypadku właściwości FixedSizeGrid
i VariableSizeGrid
użyj właściwości overscanColumnsCount
i overscanRowsCount
, aby kontrolować odpowiednio liczbę kolumn i wierszy do nadskanowania.
Podsumowanie
Jeśli nie wiesz, 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ą licznika FPS w Narzędziach deweloperskich w Chrome sprawdzać, jak wydajnie elementy są renderowane na liście.
- Dodaj atrybut
react-window
do wszystkich długich list lub siatek, które wpływają na wydajność. - Jeśli w
react-window
nie ma niektórych funkcji, rozważ użyciereact-virtualized
, jeśli nie możesz samodzielnie dodać tej funkcji. - Jeśli chcesz leniwie wczytywać elementy podczas przewijania przez użytkownika, umieść wirtualizowaną listę w tagach
react-window-infinite-loader
. - Użyj właściwości
overscanCount
w przypadku list, a właściwościoverscanColumnsCount
ioverscanRowsCount
w przypadku siatek, aby zapobiec wyświetlaniu pustych treści. Nie skanuj zbyt wielu wpisów, ponieważ negatywnie wpłynie to na wydajność.