Виртуализация длинных списков

Проблема производительности при работе с длинными списками При рендеринге длинных списков в веб-приложениях возникает критическая нагрузка на DOM. Если количество элементов превышает несколько сотен, браузер начинает заметно тормозить из-за большого количества одновременно отображаемых компонентов. Решением этой проблемы является виртуализация, когда на странице рендерятся только видимые элементы, а остальные создаются по мере прокрутки.

Использование компонента FixedSizeList из react-window MUI не содержит встроенных инструментов для виртуализации списков, но библиотека отлично интегрируется с популярными решениями, такими как react-window. Основной подход заключается в обёртывании MUI-компонентов списка (List, ListItem) внутри виртуализированного контейнера.

Пример базовой интеграции:

import React from 'react';
import { FixedSizeList } from 'react-window';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';

const items = Array.from({ length: 10000 }, (_, index) => `Элемент ${index + 1}`);

function VirtualizedList() {
  const Row = ({ index, style }) => (
    <ListItem style={style} key={index} divider>
      <ListItemText primary={items[index]} />
    </ListItem>
  );

  return (
    <List>
      <FixedSizeList
        height={600}
        width="100%"
        itemSize={46} // Высота одного элемента
        itemCount={items.length}
      >
        {Row}
      </FixedSizeList>
    </List>
  );
}

Ключевые моменты интеграции:

  • height и itemSize задают фиксированные размеры контейнера и элементов. Они критичны для правильного вычисления виртуального скролла.
  • itemCount определяет общее количество элементов, чтобы виртуализатор знал, сколько «мест» резервировать.
  • Каждый элемент должен получать style от react-window, чтобы корректно позиционироваться в контейнере.

Использование VariableSizeList для элементов разной высоты Если элементы списка имеют переменную высоту, вместо FixedSizeList используется VariableSizeList. В этом случае необходимо предоставлять функцию, которая возвращает высоту элемента по индексу:

import { VariableSizeList } from 'react-window';

const getItemSize = index => index % 2 === 0 ? 50 : 70;

<VariableSizeList
  height={600}
  width="100%"
  itemCount={items.length}
  itemSize={getItemSize}
>
  {Row}
</VariableSizeList>

Оптимизация рендеринга MUI-компонентов MUI-компоненты, такие как ListItem и ListItemText, часто содержат внутренние стили и эффекты, что может замедлять виртуализированный список при большом количестве элементов. Для оптимизации следует использовать:

  • React.memo для элементов списка, чтобы избегать лишних перерисовок.
  • Минимальное использование сложных вложенных компонентов внутри каждой строки.
  • Ограничение CSS-анимаций и теней на виртуализированных элементах.

Пример с мемоизацией:

const Row = React.memo(({ index, style }) => (
  <ListItem style={style} key={index} divider>
    <ListItemText primary={items[index]} />
  </ListItem>
));

Объединение с MUI List и стилями Чтобы сохранить стилизацию MUI, важно обернуть виртуализатор в компонент List, но не рендерить каждый ListItem напрямую в нём, иначе появятся конфликты с внутренней разметкой и padding. Структура обычно такая:

<List>
  <FixedSizeList>
    {Row}
  </FixedSizeList>
</List>

При этом padding и отступы List применяются ко всей области, а не к отдельным элементам, что предотвращает визуальные артефакты при скролле.

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

const handleItemsRendered = ({ visibleStopIndex }) => {
  if (visibleStopIndex >= items.length - 10) {
    loadMoreItems(); // Функция загрузки новых элементов
  }
};

<FixedSizeList
  height={600}
  width="100%"
  itemCount={items.length}
  itemSize={46}
  onItemsRende red={handleItemsRendered}
>
  {Row}
</FixedSizeList>

Выводы по использованию Виртуализация позволяет:

  • Значительно сократить количество одновременно рендерящихся DOM-элементов.
  • Поддерживать плавный скролл даже при десятках тысяч элементов.
  • Сохранять преимущества MUI-стилей и компонентов при работе с длинными списками.

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