Сверхбольшие таблицы и списки могут значительно снизить производительность вашего сайта. Виртуализация может помочь!
react-window
— это библиотека, которая позволяет эффективно отображать большие списки.
Вот пример списка, содержащего 1000 строк, отображаемого с помощью react-window
. Попробуйте прокручивать как можно быстрее.
Почему это полезно?
Бывают случаи, когда вам нужно отобразить большую таблицу или список, содержащий много строк. Загрузка каждого элемента такого списка может существенно повлиять на производительность.
Виртуализация списков , или «оконность», — это концепция рендеринга только того, что видно пользователю. Количество элементов, отображаемых сначала, представляет собой очень небольшую часть всего списка, и «окно» видимого контента перемещается, когда пользователь продолжает прокручивать. Это улучшает производительность рендеринга и прокрутки списка.
![Окно контента в виртуализированном списке](https://web.dev/static/articles/virtualize-long-lists-react-window/image/window-content-a-virtua-9c3c7f0cbf9a5.jpg?authuser=5&hl=ru)
Узлы DOM, выходящие из «окна», перерабатываются или немедленно заменяются более новыми элементами, когда пользователь прокручивает список вниз. Это сохраняет количество всех отображаемых элементов в зависимости от размера окна.
окно реакции
react-window
— это небольшая сторонняя библиотека, которая упрощает создание виртуализированных списков в вашем приложении. Он предоставляет ряд базовых API, которые можно использовать для различных типов списков и таблиц.
Когда использовать списки фиксированного размера
Используйте 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;
-
FixedSizeList
принимает свойстваheight
,width
иitemSize
для управления размером элементов в списке. - Функция, которая визуализирует строки, передается как дочерняя функция в
FixedSizeList
. Подробную информацию о конкретном элементе можно получить с помощью аргументаindex
(items[index]
). - Параметр
style
также передается в метод рендеринга строки, который должен быть прикреплен к элементу строки. Элементы списка позиционируются абсолютно, а их значения высоты и ширины назначаются встроенными, и за это отвечает параметрstyle
.
Пример Glitch, показанный ранее в этой статье, показывает пример компонента FixedSizeList
.
Когда использовать списки переменного размера
Используйте компонент VariableSizeList
для отображения списка элементов разных размеров. Этот компонент работает так же, как список фиксированного размера, но вместо этого ожидает функцию для свойства 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;
На следующей вставке показан пример этого компонента.
Функция размера элемента, переданная в свойство itemSize
в этом примере рандомизирует высоту строк. Однако в реальном приложении должна быть реальная логика, определяющая размеры каждого элемента. В идеале эти размеры должны быть рассчитаны на основе данных или получены из API.
Сетки
react-window
также обеспечивает поддержку виртуализации многомерных списков или сеток. В этом контексте «окно» видимого контента меняется при прокрутке пользователем по горизонтали и вертикали.
![Перемещение окна контента в виртуализированной сетке является двухмерным.](https://web.dev/static/articles/virtualize-long-lists-react-window/image/moving-window-content-a-3393c2a96ff59.jpg?authuser=5&hl=ru)
Аналогичным образом можно использовать оба компонента FixedSizeGrid
и VariableSizeGrid
в зависимости от того, может ли размер конкретных элементов списка меняться.
- Для
FixedSizeGrid
API примерно такой же, но с учетом того, что высоты, ширины и количество элементов должны быть представлены как для столбцов, так и для строк. - Для
VariableSizeGrid
ширину столбца и высоту строки можно изменить, передав функции вместо значений в соответствующие реквизиты.
Посмотрите документацию , чтобы увидеть примеры виртуализированных сеток.
Ленивая загрузка при прокрутке
Многие веб-сайты повышают производительность, ожидая загрузки и отображения элементов в длинном списке до тех пор, пока пользователь не прокрутит их вниз. Этот метод, обычно называемый «бесконечная загрузка», добавляет в список новые узлы DOM, когда пользователь прокручивает определенный порог ближе к концу. Хотя это лучше, чем загружать все элементы списка одновременно, в конечном итоге DOM будет заполнен тысячами записей строк, если пользователь прокрутит их дальше. Это может привести к чрезмерно большому размеру DOM, что начинает влиять на производительность, замедляя вычисления стилей и мутации DOM.
Следующая диаграмма может помочь обобщить это:
![Разница в прокрутке обычного и виртуализированного списка](https://web.dev/static/articles/virtualize-long-lists-react-window/image/difference-scrolling-bet-517c003019905.jpg?authuser=5&hl=ru)
Лучший подход к решению этой проблемы — продолжать использовать такую библиотеку, как react-window
для поддержания небольшого «окна» элементов на странице, а также ленивой загрузки новых записей по мере прокрутки пользователем вниз. Отдельный пакет react-window-infinite-loader
делает это возможным с помощью react-window
.
Рассмотрим следующий фрагмент кода, который показывает пример состояния, управляемого в родительском компоненте 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;
Метод loadMore
передается в дочерний элемент ListComponent
, который содержит список бесконечного загрузчика. Это важно, поскольку бесконечный загрузчик должен запустить обратный вызов для загрузки большего количества элементов, как только пользователь прокрутит определенную точку.
Вот как может выглядеть ListComponent
, отображающий список:
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;
Здесь FixedSizeList
заключен в InfiniteLoader
. Загрузчику назначены следующие реквизиты:
-
isItemLoaded
: метод, который проверяет, загружен ли определенный элемент. -
itemCount
: количество элементов в списке (или ожидаемое) -
loadMoreItems
: обратный вызов, который возвращает обещание, которое разрешается в дополнительные данные для списка.
Свойство рендеринга используется для возврата функции, которую компонент списка использует для рендеринга. Атрибуты onItemsRendered
и ref
— это атрибуты, которые необходимо передать.
Ниже приведен пример того, как бесконечная загрузка может работать с виртуализированным списком.
Прокрутка списка вниз может выглядеть так же, но теперь выполняется запрос на получение 10 пользователей из API случайных пользователей каждый раз, когда вы прокручиваете список близко к концу. Все это делается при одновременном рендеринге только одного «окна» результатов.
Проверяя index
определенного элемента, для элемента можно отобразить различное состояние загрузки в зависимости от того, был ли сделан запрос на новые записи, и элемент все еще загружается.
Например:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
сверхсканирование
Поскольку элементы в виртуализированном списке изменяются только при прокрутке пользователем, пустое пространство может ненадолго мигнуть, поскольку вот-вот отобразятся новые записи. Вы можете попробовать быстро прокрутить любой из предыдущих примеров в этом руководстве, чтобы заметить это.
Чтобы улучшить взаимодействие с пользователем при работе с виртуализированными списками, react-window
позволяет вам пересканировать элементы с помощью свойства overscanCount
. Это позволяет вам определить, сколько элементов за пределами видимого «окна» нужно отображать постоянно.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
работает как для компонентов FixedSizeList
, так и для VariableSizeList
, и имеет значение по умолчанию, равное 1. В зависимости от размера списка, а также размера каждого элемента, сверхсканирование более чем одной записи может помочь предотвратить заметное появление пустого пространства при пользователь прокручивает. Однако чрезмерное сканирование слишком большого количества записей может отрицательно повлиять на производительность. Весь смысл использования виртуализированного списка состоит в том, чтобы свести к минимуму количество записей до того уровня, который пользователь может видеть в любой момент, поэтому старайтесь свести количество пересканированных элементов к минимуму.
Для FixedSizeGrid
и VariableSizeGrid
используйте свойства overscanColumnsCount
и overscanRowsCount
, чтобы контролировать количество столбцов и строк для сверхсканирования соответственно.
Заключение
Если вы не знаете, с чего начать виртуализацию списков и таблиц в вашем приложении, выполните следующие действия:
- Измерьте производительность рендеринга и прокрутки. В этой статье показано, как можно использовать счетчик FPS в Chrome DevTools для определения эффективности отображения элементов в списке.
- Включите
react-window
для любых длинных списков или сеток, влияющих на производительность. - Если какие-то функции не поддерживаются в
react-window
, рассмотрите возможность использованияreact-virtualized
если вы не можете добавить эту функциональность самостоятельно. - Оберните свой виртуальный список с помощью
react-window-infinite-loader
если вам нужно отложенно загружать элементы при прокрутке пользователем. - Используйте свойство
overscanCount
для своих списков, а также свойстваoverscanColumnsCount
иoverscanRowsCount
для своих сеток, чтобы предотвратить появление пустого содержимого. Не сканируйте слишком много записей, так как это отрицательно скажется на производительности.