Skip to main content

Оптимізація довгих списків

1) Віртуалізація (windowing) - must-have

Рендеримо лише видимі елементи + невеликий «overscan».

Бібліотеки:

  • react-window (легка, сучасна)
  • react-virtualized (багатша, важча)
  • @tanstack/react-virtual (головоломка, але гнучка)

Приклад з react-window (фіксована висота):

javascript
import { FixedSizeList as List } from 'react-window'; import React from 'react'; const Row = React.memo(({ index, style, data }: any) => { const item = data.items[index]; return ( <div style={style} onClick={() => data.onSelect(item.id)}> {item.title} </div> ); }, (prev, next) => prev.data.items[prev.index] === next.data.items[next.index]); export default function Virtualized({ items, onSelect }: { items: any[], onSelect: (id:string)=>void }) { const itemData = React.useMemo(() => ({ items, onSelect }), [items, onSelect]); return ( <List height={600} itemCount={items.length} itemSize={48} width="100%" overscanCount={6} itemData={itemData} > {Row} </List> ); }

Змінна висота? Використовуйте VariableSizeList + вимірювання висоти (наприклад, через ResizeObserver) і кешуйте getItemSize.


2) Пагінація і «нескінченний скрол»

  • Завантаження робіть порціями (limit/offset або cursor).
  • Рендеріть «плоским» масивом; нові пачки - append.
  • Для детекту кінця - IntersectionObserver (без постійних обробників scroll).

3) Мінімізуємо перерендери елементів

  • Кожен айтем - окремий мемо-компонент: React.memo(Row, areEqual).
  • Пропси елементів - стабільні за посиланням:
    • обробники через useCallback;
    • складні об'єкти/масиви через useMemo;
    • передавайте itemId, а не замикайте item в inline-колбеку.
  • Не використовуйте індекс як key при вставках/видаленнях/перестановках - беріть стабільний id.

4) Розділяємо обчислення і представлення

  • Фільтрацію, сортування, агрегації - робіть один раз і кешуйте useMemo.
  • При введенні в поле фільтра використовуйте useDeferredValue або обгортайте оновлення в startTransition, щоб список не «лагав»:
javascript
const [query, setQuery] = React.useState(''); const deferredQuery = React.useDeferredValue(query); // згладжує фільтрацію const filtered = React.useMemo( () => items.filter(x => x.title.includes(deferredQuery)), [items, deferredQuery] );

5) Зображення і важкі клітинки

  • Вмикайте ліниве завантаження: <img loading="lazy" ... />.
  • Плейсхолдери/скелетони замість важкого контенту до появи даних.
  • Уникайте дорогого DOM і складного CSS усередині клітинки (тіні, фільтри).

6) Події скролу

  • Не вішайте «ручний» onScroll на вікно. Якщо потрібно - passive listeners і requestAnimationFrame + throttle.
  • При віртуалізації бібліотека вже оптимізує скрол - менше самодіяльності.

7) Контейнер списку

  • Задайте явну висоту/max-height і overflow: auto; - браузеру простіше.
  • Для вкладених списків використовуйте CSS contain: content; (обмежує область рендерингу).

8) Серверний рендер і гідратація

  • Для SEO/SSR великих списків - віддавайте обмежену кількість елементів, решту довантажуйте на клієнті.
  • У Next.js можна стрімити і довантажувати чанки, а клієнт - віртуалізувати.

9) Кеш розмірів і реюз DOM

  • При змінній висоті - тримайте кеш вимірювань (id → height).
  • Уникайте повного скидання кешу при часткових змінах даних.

10) Діагностика

  • React DevTools Profiler: шукайте «гарячі» компоненти.
  • Увімкніть мітки в логах рендера клітинок (див. console.log('Row', index)) локально.
  • Перевірте, чи не створюєте нові об'єкти/функції в JSX.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.