# Швидкий JavaScript ListView у 2026: Windowing, Віртуалізація та Оптимізація Продуктивності > Опануйте техніки windowing та віртуалізації для рендерингу тисяч елементів у JavaScript. TanStack Virtual, react-virtuoso та найкращі практики для ефективних списків у 2026 році. - Published: 2026-09-15 - Updated: 2026-09-15 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Рендеринг тисяч елементів у JavaScript ListView без віртуалізації змушує браузер створювати тисячі DOM-вузлів, що призводить до повільного початкового рендерингу, лагів при прокручуванні та споживання пам'яті, яке може спричинити збій мобільних пристроїв. Швидкі реалізації JavaScript ListView у 2026 році вирішують цю проблему через windowing: рендеринг лише 10-20 елементів, видимих у viewport, одночасно симулюючи область прокрутки, що містить усі елементи. > **Windowing одним реченням** > > Windowing рендерить лише видимі елементи плюс невеликий буфер (overscan), зменшуючи кількість DOM-вузлів з 10 000 до приблизно 20, незалежно від розміру списку. ## Чому продуктивність браузера падає при великих списках Кожен DOM-вузол споживає пам'ять і вимагає від браузера відстежувати його позицію під час обчислень макету. Список із 10 000 елементів з помірно складною розміткою (аватар, два текстові спани, кнопка) створює понад 40 000 DOM-вузлів. Рендеринговий рушій Chrome перераховує стилі та позиції для всіх них при кожній події прокрутки. Результат: початковий рендеринг займає 2-4 секунди, прокрутка падає до 15fps, а використання пам'яті зростає до понад 500MB. На мобільному Safari вкладка часто повністю падає. Віртуалізація виправляє це, підтримуючи постійну кількість DOM-вузлів. Незалежно від того, чи масив даних містить 100 чи 100 000 елементів, рендерений DOM залишається на рівні приблизно 20-30 вузлів, зберігаючи плоске споживання пам'яті та продуктивність прокрутки на рівні 60fps. ```typescript // VirtualizedList.tsx import { useVirtualizer } from '@tanstack/react-virtual' import { useRef } from 'react' interface Item { id: string name: string email: string } export function VirtualizedList({ items }: { items: Item[] }) { const parentRef = useRef(null) // Create virtualizer instance with estimated row height const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, // estimated height in pixels overscan: 5, // render 5 extra items above/below viewport }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const item = items[virtualRow.index] return (

{item.name}

{item.email}

) })}
) } ``` Ця реалізація рендерить лише видимі рядки плюс буфер overscan. Зовнішній контейнер встановлює загальну висоту прокрутки, змушуючи смугу прокрутки поводитися так, ніби всі елементи існують у DOM. ## TanStack Virtual: Безголова архітектура для 2026 року [TanStack Virtual](https://tanstack.com/virtual/latest) надає framework-агностичні примітиви віртуалізації. На відміну від react-window чи react-virtuoso, не пропонує готових компонентів - лише хуки, що обчислюють які елементи рендерити і де їх позиціонувати. Ця безголова архітектура означає повний контроль над стилями та структурою DOM. Поточна версія (3.13) підтримує React, Vue, Solid, Svelte та чистий JavaScript. Ключові параметри конфігурації: | Параметр | Призначення | Значення за замовчуванням | |--------|---------|--------| | `count` | Загальна кількість елементів | обов'язковий | | `estimateSize` | Функція, що повертає орієнтовну висоту/ширину елемента | обов'язковий | | `overscan` | Додаткові елементи, що рендеряться за межами viewport | 1 | | `getScrollElement` | Повертає контейнер прокрутки | обов'язковий | | `measureElement` | Власна функція вимірювання для динамічних розмірів | авто | Значення `overscan` безпосередньо впливає на плавність прокрутки. Занадто низьке значення (0-2) спричиняє білі спалахи під час швидкої прокрутки. Занадто високе (20+) нівелює переваги віртуалізації. Для більшості застосунків 5-10 забезпечує оптимальний баланс. ```typescript // DynamicHeightList.tsx import { useVirtualizer } from '@tanstack/react-virtual' import { useRef, useCallback } from 'react' interface Message { id: string content: string timestamp: number } export function ChatList({ messages }: { messages: Message[] }) { const parentRef = useRef(null) // measureElement enables dynamic height measurement const virtualizer = useVirtualizer({ count: messages.length, getScrollElement: () => parentRef.current, estimateSize: () => 80, // rough estimate, actual measured later overscan: 8, }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const message = messages[virtualRow.index] return (

{new Date(message.timestamp).toLocaleTimeString()}

{message.content}

) })}
) } ``` Ref `measureElement` дозволяє автоматичне вимірювання висоти після рендерингу. Це обробляє змінну довжину контенту без необхідності ручних обчислень висоти. ## react-virtuoso: Багатофункціональна альтернатива [react-virtuoso](https://virtuoso.dev/) застосовує протилежний підхід: бібліотека компонентів з усім необхідним. Там, де TanStack Virtual вимагає побудови контейнера прокрутки та логіки позиціонування, react-virtuoso обробляє це внутрішньо. Компроміс — розмір пакету (37KB проти 12KB для TanStack Virtual), але значно менше шаблонного коду для типових патернів, як-от групові списки, липкі заголовки та інтерфейси чату зі зворотною прокруткою. ```typescript // VirtuosoList.tsx import { Virtuoso } from 'react-virtuoso' interface User { id: string name: string department: string } export function UserDirectory({ users }: { users: User[] }) { return ( (
{user.name.charAt(0)}

{user.name}

{user.department}

)} /> ) } ``` Це досягає того ж результату віртуалізації з меншою кількістю коду. react-virtuoso автоматично вимірює елементи, обробляє динамічні висоти та забезпечує збереження позиції прокрутки. Під час [підготовки до співбесіди з продуктивності React](/technologies/react-next/interview-questions/styling-css-in-js), розуміння коли обирати кожну бібліотеку демонструє архітектурну обізнаність. TanStack Virtual підходить для застосунків, що вже використовують TanStack Table або потребують framework-агностичний код. react-virtuoso підходить командам, що пріоритезують швидкість розробки над розміром пакету. ## Типові пастки та рішення ### Стрибки позиції прокрутки при зображеннях Зображення завантажуються асинхронно, змінюючи висоту рядка після початкового вимірювання. Віртуалізатор не може передбачити цю зміну, спричиняючи зсув контенту нижче. Рішення: резервування місця для зображень за допомогою aspect-ratio або фіксованих розмірів. ```typescript // ImageListItem.tsx interface ImageItem { id: string src: string title: string } export function ImageListItem({ item }: { item: ImageItem }) { return (
{/* Reserve 80x80px space regardless of load state */}
{item.title}

{item.title}

) } ``` ### Втрата стану під час прокрутки Віртуалізовані елементи розмонтовуються при прокручуванні за межі видимої області. Будь-який локальний стан (поля форм, розгорнуті акордеони) зникає. Рішення: перенесення стану до батьківського компонента або бібліотеки управління станом з ключем за ID елемента. ```typescript // ExpandableList.tsx import { useState, useCallback, useRef } from 'react' import { useVirtualizer } from '@tanstack/react-virtual' interface FAQ { id: string question: string answer: string } export function FAQList({ faqs }: { faqs: FAQ[] }) { // State lives in parent, survives row unmounting const [expandedIds, setExpandedIds] = useState>(new Set()) const parentRef = useRef(null) const toggleExpanded = useCallback((id: string) => { setExpandedIds(prev => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) }, []) const virtualizer = useVirtualizer({ count: faqs.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, overscan: 5, }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const faq = faqs[virtualRow.index] const isExpanded = expandedIds.has(faq.id) return (
) })}
) } ``` ### Нестабільні ключі, що спричиняють повторні рендери Використання індексів масиву як ключів працює, поки список не буде пересортовано. Елементи отримують неправильний стан, спричиняючи візуальні помилки та марні рендери. Завжди слід використовувати стабільні, унікальні ідентифікатори з самих даних: ```typescript // Correct: stable key from data {virtualizer.getVirtualItems().map((virtualRow) => { const item = items[virtualRow.index] return
...
// id from database/API })} // Wrong: index changes when list reorders {virtualizer.getVirtualItems().map((virtualRow, index) => { return
...
// causes bugs on reorder })} ``` ## Порівняння бібліотек для проектів 2026 року Вибір правильної бібліотеки віртуалізації залежить від вимог проекту. Це порівняння відображає екосистему 2026 року: | Критерій | TanStack Virtual | react-virtuoso | react-window | |----------|-----------------|----------------|---------------| | Розмір пакету | 12KB | 37KB | 6KB | | Динамічні висоти | Ручне налаштування | Автоматично | Не підтримується | | TypeScript | Нативний | Нативний | Пакет @types | | Підтримка фреймворків | React, Vue, Solid, Svelte | Тільки React | Тільки React | | Підтримка | Активна (2026) | Активна (2026) | Режим підтримки | | Крива навчання | Вища | Нижча | Найнижча | react-window залишається варіантом для простих списків з фіксованою висотою, де важливий розмір пакету. Однак його статус режиму підтримки та відсутність підтримки динамічних висот робить його непридатним для нових проектів зі складними вимогами. Для [побудови продуктивних React-застосунків](/blog/react-next/react-testing-2026-vitest-rtl-best-practices), розуміння компромісів віртуалізації демонструє усвідомлення продуктивності, яке шукають інтерв'юери. ## Вимірювання покращень продуктивності Кількісна оцінка переваг віртуалізації вимагає послідовної методології вимірювання. Панель Performance у Chrome DevTools надає відповідні метрики. Ключові вимірювання до/після віртуалізації: - **First Contentful Paint (FCP)**: Час до першого рендерингу контенту. Віртуалізація зменшує його, обмежуючи початкове створення DOM. - **Total Blocking Time (TBT)**: Блокування основного потоку під час завантаження. Менше DOM-вузлів означає менше обчислень стилю/макету. - **Використання пам'яті**: Знімок розміру купи в панелі Memory. Має залишатися постійним незалежно від довжини списку. - **FPS прокрутки**: Запис Performance trace під час прокрутки. Частота кадрів має залишатися на рівні 60fps. ```typescript // PerformanceMonitor.tsx import { useEffect, useRef } from 'react' export function useScrollFPS(containerRef: React.RefObject) { const frameCountRef = useRef(0) const lastTimeRef = useRef(performance.now()) useEffect(() => { const container = containerRef.current if (!container) return let rafId: number const measureFrame = () => { frameCountRef.current++ const now = performance.now() if (now - lastTimeRef.current >= 1000) { console.log(`Scroll FPS: ${frameCountRef.current}`) frameCountRef.current = 0 lastTimeRef.current = now } rafId = requestAnimationFrame(measureFrame) } const handleScroll = () => { if (!rafId) { rafId = requestAnimationFrame(measureFrame) } } container.addEventListener('scroll', handleScroll, { passive: true }) return () => { container.removeEventListener('scroll', handleScroll) cancelAnimationFrame(rafId) } }, [containerRef]) } ``` Цей хук логує FPS прокрутки під час розробки. Продакшн-застосунки повинні використовувати інструменти спостережуваності, як-от Sentry Performance або власну телеметрію. ## Ключові висновки для продуктивності списків JavaScript - Віртуалізація зменшує DOM-вузли з тисяч до приблизно 20, зберігаючи постійну пам'ять та продуктивність прокрутки незалежно від розміру даних - TanStack Virtual (12KB) надає безголові примітиви для максимального контролю в React, Vue, Solid та Svelte - react-virtuoso (37KB) пропонує готові компоненти з автоматичною обробкою динамічних висот - react-window залишається в режимі підтримки з 2022 року і не підтримує динамічні висоти - Встановлення overscan між 5-10 забезпечує оптимальну плавність прокрутки без нівелювання переваг віртуалізації - Резервування розмірів зображень запобігає стрибкам позиції прокрутки під час асинхронного завантаження - Перенесення розгортаних/редагованих станів до батьківського компонента, оскільки віртуалізовані рядки розмонтовуються під час прокрутки - Використання стабільних ключів з ID даних, ніколи індексів масиву, для запобігання невідповідностям стану при пересортуванні - Вимірювання FCP, TBT, пам'яті та FPS прокрутки до/після реалізації для кількісної оцінки покращень --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/node-nestjs/fast-javascript-listview-2026-windowing-virtualization-performance