← Назад к вопросам

Как оптимизировать рендеринг большого списка в React?

1.8 Middle🔥 221 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация рендеринга больших списков в React

Рендеринг больших списков — частая проблема в фронтенде. Если нерационально подойти, пользователь видит лаги, фризы и плохой UX. Есть несколько проверенных техник для решения этой проблемы.

Проблема: почему падает производительность

Когда список содержит 1000+ элементов, React должен рендерить каждый элемент в DOM. Это требует:

  • Парсинга JSX каждого элемента
  • Создания компонентов в памяти
  • Добавления в DOM-дерево
  • Переразметки и переотрисовки браузером

Результат: падает FPS, браузер зависает.

Решение 1: Виртуализация (Virtual Scrolling)

Идея простая: рендерим только видимые элементы + буфер за границами экрана.

import { FixedSizeList } from 'react-window';

function LargeList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style} className="item">
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}

Плюсы: Работает даже с 100,000 элементами, минимальная память. Минусы: Требует фиксированной высоты элемента, сложнее с динамическим контентом.

Решение 2: Pагинация

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

function PaginatedList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);

  const loadMore = async () => {
    setIsLoading(true);
    const newItems = await fetchItems(page, limit: 50);
    setItems(prev => [...prev, ...newItems]);
    setPage(page + 1);
    setIsLoading(false);
  };

  return (
    <div>
      {items.map(item => <div key={item.id}>{item.name}</div>)}
      <button onClick={loadMore} disabled={isLoading}>
        {isLoading ? 'Загрузка...' : 'Загрузить еще'}
      </button>
    </div>
  );
}

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

Решение 3: Мемоизация компонентов

Используем React.memo и useMemo для предотвращения ненужных ре-рендеров.

const ListItem = React.memo(({ item, onSelect }) => {
  return (
    <div onClick={() => onSelect(item.id)}>
      {item.name}
    </div>
  );
});

function ListWithMemo({ items, onSelect }) {
  const memoizedOnSelect = useCallback((id) => {
    onSelect(id);
  }, [onSelect]);

  return (
    <div>
      {items.map(item => (
        <ListItem
          key={item.id}
          item={item}
          onSelect={memoizedOnSelect}
        />
      ))}
    </div>
  );
}

Плюсы: Простое добавление, работает с любым списком. Минусы: Не поможет, если реально много элементов (100+), нужна комбинация методов.

Мой рекомендуемый подход

Комбинирую методы в зависимости от сценария:

  • 100-500 элементов: Мемоизация + фильтрация на клиенте
  • 500-5000: Пагинация + мемоизация
  • 5000+: Виртуализация (react-window или react-virtualized)

Для максимальной производительности всегда профилирую с React DevTools Profiler.