useVirtualizer для виртуализации

Виртуализация — это техника оптимизации рендеринга длинных списков или сеток элементов, при которой в DOM одновременно отображается только часть видимых элементов. Это значительно повышает производительность, снижает нагрузку на браузер и ускоряет взаимодействие с интерфейсом. Библиотека React Aria предоставляет хук useVirtualizer, который реализует гибкую и эффективную виртуализацию списков и сеток.

Импорт и базовое использование

Для работы с виртуализированными списками сначала нужно импортировать хук:

import { useVirtualizer } from '@react-aria/virtualizer';

Основной принцип работы useVirtualizer заключается в том, что он возвращает массив видимых элементов и вычисляет смещения для корректного позиционирования, имитируя полный список в DOM.

Простейший пример виртуализированного списка:

function VirtualList({ items }) {
  const rowVirtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => document.getElementById('scroll-container'),
    estimateSize: () => 50, // предполагаемая высота элемента
  });

  return (
    <div id="scroll-container" style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: rowVirtualizer.totalSize, position: 'relative' }}>
        {rowVirtualizer.virtualItems.map(virtualRow => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualRow.start}px)`
            }}
          >
            {items[virtualRow.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

Пояснение ключевых параметров

  • count — общее количество элементов в списке. useVirtualizer использует это значение для расчета диапазона видимых элементов.

  • getScrollElement — функция, возвращающая контейнер с прокруткой. Важно передавать реальный DOM-элемент.

  • estimateSize — функция или константа, определяющая приблизительную высоту (или ширину для горизонтальной прокрутки) каждого элемента. Это позволяет виртуализатору корректно вычислять смещение и размер контейнера.

  • virtualItems — массив объектов, каждый из которых содержит:

    • index — индекс элемента в исходном массиве.
    • start — смещение элемента относительно начала контейнера.
    • size — фактический размер элемента после измерения.

Управление горизонтальной и вертикальной виртуализацией

useVirtualizer поддерживает как вертикальную, так и горизонтальную виртуализацию. По умолчанию используется вертикальная ориентация, но для горизонтальной можно указать horizontal: true:

const columnVirtualizer = useVirtualizer({
  count: items.length,
  horizontal: true,
  getScrollElement: () => document.getElementById('horizontal-scroll'),
  estimateSize: () => 120
});

В этом случае смещение start будет рассчитываться по оси X, а трансформация будет translateX.

Динамическая высота элементов

В реальных приложениях элементы списка могут иметь переменную высоту. useVirtualizer позволяет учитывать это через функцию измерения:

const rowVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => document.getElementById('scroll-container'),
  estimateSize: index => items[index].height // возвращает конкретную высоту для каждого элемента
});

После рендеринга виртуализатор автоматически обновляет размеры и перерассчитывает смещения для точного позиционирования элементов.

Поддержка прокрутки к элементу

Для программного скроллинга к определённому элементу можно использовать метод scrollToIndex:

rowVirtualizer.scrollToIndex(50, { align: 'center' });

Параметры:

  • index — индекс элемента, к которому нужно проскроллить.
  • align — позиционирование элемента в области прокрутки (start, center, end или auto).

Это позволяет легко реализовать функции вроде «перейти к новому сообщению» или «найти элемент по поиску».

Интеграция с React Aria коллекциями

useVirtualizer отлично сочетается с коллекциями React Aria (useListState, useGridState) для создания полностью доступных и оптимизированных списков и таблиц. Например:

import { useListState } from '@react-stately/list';

function AccessibleVirtualList({ items }) {
  const state = useListState({ items });
  const rowVirtualizer = useVirtualizer({
    count: state.collection.size,
    getScrollElement: () => document.getElementById('scroll-container'),
    estimateSize: () => 50
  });

  return (
    <div id="scroll-container" style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: rowVirtualizer.totalSize, position: 'relative' }}>
        {rowVirtualizer.virtualItems.map(virtualRow => {
          const item = state.collection.getItem(virtualRow.index);
          return (
            <div
              key={item.key}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${virtualRow.start}px)`
              }}
            >
              {item.rendered}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Такое сочетание обеспечивает не только высокую производительность, но и полное соответствие стандартам доступности, включая правильную работу с клавиатурой и поддержкой ARIA-атрибутов.

Оптимизация рендеринга

  • Мемоизация элементов — оборачивать виртуализированные компоненты в React.memo для предотвращения лишних перерисовок.
  • Предварительная загрузка элементов — с помощью свойства overscan можно рендерить несколько элементов за пределами видимой области, уменьшая мерцание при прокрутке:
const rowVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => document.getElementById('scroll-container'),
  estimateSize: () => 50,
  overscan: 5
});
  • Динамическое измерение размеров — использование ResizeObserver позволяет автоматически обновлять размеры элементов при изменении контента.

Поддержка Grid и сложных макетов

Для сеток (grid) useVirtualizer поддерживает двухмерную виртуализацию с указанием количества столбцов и размеров элементов:

const gridVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => document.getElementById('grid-scroll'),
  estimateSize: () => 100,
  horizontal: false,
  getItemKey: index => items[index].id,
  columns: 4
});

Каждый элемент получает позицию row и column, что позволяет точно отображать сетку с тысячами элементов без падения производительности.

Итог

useVirtualizer предоставляет полный набор инструментов для построения высокопроизводительных списков и сеток, включая поддержку переменной высоты элементов, горизонтальной прокрутки, интеграцию с коллекциями React Aria и управление скроллингом. Правильная настройка параметров estimateSize, overscan и методов прокрутки позволяет создать плавный и отзывчивый интерфейс даже для огромных наборов данных.