Слишком большие таблицы и списки могут значительно замедлить работу вашего сайта. Виртуализация может помочь!
react-window
— это библиотека, позволяющая эффективно отображать большие списки.
Вот пример списка, содержащего 1000 строк, который отображается с помощью react-window
. Попробуйте прокручивать как можно быстрее.
Почему это полезно?
Иногда может потребоваться отобразить большую таблицу или список, содержащий много строк. Загрузка каждого элемента такого списка может существенно снизить производительность.
Виртуализация списков , или «оконный режим», — это концепция отображения только того, что видно пользователю. Количество элементов, отображаемых в первую очередь, составляет лишь очень малую часть всего списка, и «окно» видимого содержимого перемещается по мере прокрутки пользователем. Это повышает производительность как отображения, так и прокрутки списка.

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
также поддерживает виртуализацию многомерных списков (сеток). В этом контексте «окно» видимого контента изменяется при горизонтальной и вертикальной прокрутке пользователем.

Аналогично, компоненты FixedSizeGrid
и VariableSizeGrid
могут использоваться в зависимости от того, может ли изменяться размер конкретных элементов списка.
- Для
FixedSizeGrid
API примерно такой же, за исключением того, что необходимо указать высоту, ширину и количество элементов как для столбцов, так и для строк. - Для
VariableSizeGrid
ширину столбцов и высоту строк можно изменить, передав функции вместо значений в соответствующие свойства.
Ознакомьтесь с документацией , чтобы увидеть примеры виртуализированных сеток.
Ленивая загрузка при прокрутке
Многие веб-сайты повышают производительность, ожидая загрузки и отображения элементов длинного списка, пока пользователь не прокрутит страницу вниз. Этот метод, обычно называемый «бесконечной загрузкой», добавляет новые узлы DOM в список, когда пользователь прокручивает страницу до определённого значения ближе к концу. Хотя это лучше, чем одновременная загрузка всех элементов списка, при прокрутке страницы выше этого значения DOM всё равно заполняется тысячами записей строк. Это может привести к чрезмерно большому размеру DOM, что начинает влиять на производительность, замедляя вычисление стилей и мутации DOM.
Следующая диаграмма может помочь обобщить это:

Лучший подход к решению этой проблемы — продолжать использовать библиотеку, например, 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
: обратный вызов, который возвращает обещание, разрешающееся в дополнительные данные для списка
Свойство render используется для возврата функции, которую компонент списка использует для рендеринга. Атрибуты 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
для сеток, чтобы избежать появления пустого содержимого. Не пересканируйте слишком много записей, так как это негативно скажется на производительности.