Оптимізація довгих списків
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
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.