Skip to content
О сайте Блог Обучение Исследовать узоры Case studies
Содержание
  • Почему это полезно?
  • Библиотека react-window
    • Когда использовать списки фиксированного размера
    • Когда использовать списки переменного размера
    • Таблицы
  • Отложенная загрузка при прокрутке
  • Избыточное сканирование
  • Вывод

Виртуализация больших списков с помощью библиотеки react-window

Сверхбольшие таблицы и списки могут значительно снижать производительность сайта. В этом случае на помощь может прийти виртуализация.

Apr 29, 2019
Available in: English, Español, Português, 中文 и 한국어
Appears in: React
Houssein Djirdeh
Houssein Djirdeh
TwitterGitHubGlitchHomepage
Jason Miller
Jason Miller
TwitterGitHubHomepage
Содержание
  • Почему это полезно?
  • Библиотека react-window
    • Когда использовать списки фиксированного размера
    • Когда использовать списки переменного размера
    • Таблицы
  • Отложенная загрузка при прокрутке
  • Избыточное сканирование
  • Вывод

react-window — это библиотека, с помощью которой можно эффективно отображать большие списки.

Вот пример списка, в котором 1000 строк, отображаемых с помощью библиотеки react-window. Попробуйте прокрутить этот список как можно быстрее.

Почему это полезно? #

Иногда требуется отобразить большую таблицу или список с большим количеством строк. Загрузив все элементы такого списка, можно сильно ухудшить производительность страницы.

Виртуализация списков или «кадрирование» — это концепция, согласно которой отображают только те элементы, которые видны пользователю. Сначала отображают очень небольшое количество элементов списка. Когда пользователь прокручивает список, «окно» видимого содержимого перемещается. Это повышает производительность списка как при его отображении, так и при прокрутке.

Окно содержимого в виртуализированном списке
Перемещение «окна» содержимого в виртуализированном списке

Когда пользователь прокручивает список вниз, узлы DOM, которые выходят за пределы «окна», повторно используются или немедленно заменяются новыми элементами. Это позволяет поддерживать постоянное количество отображаемых элементов, зависящее от размера окна.

Библиотека react-window #

react-window — это небольшая сторонняя библиотека, упрощающая создание виртуализированных списков в приложении. Имеющиеся в ней базовые API можно использовать для работы со списками и таблицами различных типов.

Когда использовать списки фиксированного размера #

Используйте компонент FixedSizeList для длинных одномерных списков с элементами одинакового размера.

import React from 'react';
import { FixedSizeList } from 'react-window';

const items = [...] // Список элементов

const Row = ({ index, style }) => (
<div style={style}>
{/* Определение компонента строки с использованием массива 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.

Внимание

Не назначайте свойства height и width списку или элементу списка с помощью внешнего файла CSS. Они будут проигнорированы, так как эти атрибуты стиля применяются в коде.

В приведенном выше примере из Glitch показан компонент FixedSizeList.

Когда использовать списки переменного размера #

Для отображения списков с элементами разного размера используйте компонент VariableSizeList. Он работает точно так же, как списки элементов фиксированного размера, но вместо определенного значения в свойство itemSize нужно передавать функцию.

import React from 'react';
import { VariableSizeList } from 'react-window';

const items = [...] // Список элементов

const Row = ({ index, style }) => (
<div style={style}>
{/* Определение компонента строки с использованием массива items[index] */}
</div>
);

const getItemSize = index => {
// Возвращаем размер массива items[index]
}

const ListComponent = () => (
<VariableSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={getItemSize}
>

{Row}
</VariableSizeList>
);

export default ListComponent;

Ниже показан пример этого компонента.

В этом примере функция размера элемента, передаваемая в свойство itemSize, возвращает случайные значения высоты строк. Однако в реальном приложении следует использовать правильную логику, определяющую размеры каждого элемента. В идеальном случае эти размеры следует вычислять на основе данных или получать из API.

Компоненты FixedSizeList и VariableSizeList поддерживают горизонтальные списки при использовании свойства layout="horizontal". Пример такого списка см. в документации.

Таблицы #

Библиотека react-window также поддерживает виртуализацию многомерных списков или таблиц. В этом контексте отображаемое содержимое «окна» изменяется, когда пользователь выполняет прокрутку по горизонтали и вертикали.

Перемещение окна содержимого в виртуализированной таблице в двух измерениях
Перемещение «окна» содержимого в виртуализированной таблице в двух измерениях

В этом случае также можно использовать компоненты FixedSizeGrid и VariableSizeGrid (в зависимости от того, изменяется ли размер элементов списка).

  • Для компонента FixedSizeGrid API практически такой же, но нужно указывать значения высоты, ширины и количества элементов как для столбцов, так и для строк.
  • Для компонента VariableSizeGrid можно изменять ширину столбцов и высоту строк, передавая функции, а не значения в соответствующие свойства этого компонента.

Примеры виртуализированных таблиц см. в документации.

Помимо базовых компонентов для создания эффективных списков и таблиц, библиотека react-window предоставляет и другие возможности, например функции прокрутки к определенному элементу или отображения индикатора, когда пользователь прокручивает список или таблицу. Соответствующие примеры см. в документации.

Отложенная загрузка при прокрутке #

Многие веб-сайты повышают производительность, откладывая загрузку и рендеринг элементов длинного списка, пока пользователь не прокрутит список до этих элементов. При использовании этого метода, обычно называемого «бесконечной загрузкой», новые узлы 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: [], // Здесь мы создаем первоначальный список
moreItemsLoading: false,
hasNextPage: true
};

this.loadMore = this.loadMore.bind(this);
}

loadMore() {
// Метод для получения новых записей списка
}

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 }) => (
{/* Определяем компонент строки с использованием массива 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 пользователей из Random User API каждый раз, когда пользователь прокручивает содержимое окна в сторону конца списка. При этом выполняется рендеринг не более одного «окна» результатов за раз.

Проверяя значение index определенного элемента, можно отображать другое состояние загрузки для элемента в зависимости от того, был ли сделан запрос на получение новых записей и загружается ли все еще элемент.

Пример:

const Row = ({ index, style }) => {
const itemLoading = index === items.length;

if (itemLoading) {
// Возвращаем информацию о состоянии загрузки
} else {
// Возвращаем элемент
}
};

Избыточное сканирование #

Так как элементы в виртуализированном списке сменяются, только когда пользователь прокручивает список, перед отображением новых записей на короткое время может отображаться пустое пространство. Чтобы увидеть это, попробуйте быстро прокрутить любой список в предыдущих примерах в данном руководстве.

Чтобы повысить удобство для пользователей, работающих с виртуализированными списками, библиотека react-window позволяет выполнять избыточное сканирование элементов с использованием свойства overscanCount. С помощью этого свойства можно указать, какое количество элементов, находящихся за пределами видимого «окна», нужно всегда отображать.

<FixedSizeList
//...
overscanCount={4}
>

{...}
</FixedSizeList>

Свойство overscanCount работает как для компонента FixedSizeList, так и для компонента VariableSizeList; по умолчанию оно имеет значение 1. В зависимости от величины списка и размера каждого элемента, выполняя избыточное сканирование нескольких записей, можно предотвратить заметное мигание пустого пространства, когда пользователь прокручивает список. Однако избыточное сканирование слишком большого количества записей может снизить производительность. Весь смысл виртуализированного списка в том, чтобы использовать только то количество записей, которое можно отображать для пользователя в тот или ной момент времени, поэтому старайтесь выполнять избыточное сканирование для минимально возможного количества элементов.

В компонентах FixedSizeGrid и VariableSizeGrid можно управлять количеством столбцов и строк, для которых необходимо выполнять избыточное сканирование. Для этого используют свойства overscanColumnsCount и overscanRowsCount соответственно.

Вывод #

Если вы не знаете, с чего начать виртуализацию списков и таблиц в приложении, выполните указанные ниже действия.

  1. Измерьте производительность рендеринга и прокрутки. В этой статье рассказывается, как с помощью измерителя FPS в Chrome DevTools оценить эффективность рендеринга элементов в списке.
  2. Добавьте библиотеку react-window для работы с длинными списками или таблицами, снижающими производительность.
  3. Если вам нужны функции, не поддерживаемые в библиотеке react-window, и вы не можете самостоятельно реализовать их, попробуйте использовать библиотеку react-virtualized.
  4. Если нужно выполнять отложенную загрузку элементов по мере прокручивания списка пользователем, вложите виртуализированный список в элемент react-window-infinite-loader.
  5. Используя свойство overscanCount для списков (или свойства overscanColumnsCount и overscanRowsCount для таблиц), можно предотвратить мигание пустого содержимого. Не выполняйте избыточное сканирование для слишком большого количества записей, так как это снизит производительность.
Последнее обновление: Apr 29, 2019 — Улучшить статью
Return to all articles
Поделиться
подписаться

Contribute

  • Сообщить об ошибке
  • Просмотреть исходный код

Дополнительная информация

  • developer.chrome.com
  • Новости Chrome
  • Разборы конкретных случаев
  • Подкасты
  • Шоу

Соцсети

  • Twitter
  • YouTube
  • Google Developers
  • Chrome
  • Firebase
  • Google Cloud Platform
  • Все продукты
  • Условия и конфиденциальность
  • Правила сообщества

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies.