Wydajna lista JavaScript ListView w 2026: Techniki wirtualizacji i optymalizacji
Opanuj wirtualizację list JavaScript z TanStack Virtual, react-virtuoso i react-window. Renderuj ponad 100 tysięcy elementów płynnie dzięki technikom windowing.

Wydajne renderowanie list JavaScript ListView staje się krytyczne, gdy aplikacje muszą wyświetlać tysiące elementów bez zamrażania przeglądarki. Wirtualizacja list, technika renderowania tylko widocznych elementów przy jednoczesnym recyklingu węzłów DOM, przekształca wolne przewijanie w płynne interakcje.
Wirtualizacja renderuje tylko elementy widoczne w viewport plus mały bufor overscan. Lista 100 000 elementów z 10 widocznymi wierszami tworzy zaledwie 15-20 węzłów DOM zamiast 100 000, redukując zużycie pamięci z setek megabajtów do kilobajtów.
Dlaczego liczba węzłów DOM niszczy wydajność przewijania
Przeglądarki mają problemy z dużymi drzewami DOM. Każdy węzeł wymaga pamięci dla samego elementu, jego obliczonych stylów i danych layoutu. Powyżej 5000 węzłów reflow stają się zauważalne. Przy 50 000 węzłach zdarzenia przewijania mogą zajmować ponad 100ms, powodując widoczne opóźnienia.
Koszt wydajnościowy rośnie nieliniowo. Pipeline renderowania Chrome musi:
- Wykonać handlery zdarzeń JavaScript
- Przeliczyć style dla dotkniętych elementów
- Obliczyć pozycje layoutu
- Narysować piksele na warstwach
- Skomponować warstwy do finalnego wyświetlenia
Wirtualizacja całkowicie eliminuje kroki 2-4 dla elementów poza ekranem. Przeglądarka utrzymuje stały rozmiar DOM niezależnie od długości danych.
TanStack Virtual: Standard wirtualizacji bez interfejsu
TanStack Virtual dostarcza prymitywy wirtualizacji niezależne od frameworka. Wersja 3.13 (wydana w sierpniu 2026) przyniosła znaczące usprawnienia wydajności: zimny mount dla 100 000 elementów spadł z 6,1ms do 4,5ms, a najgorszy scenariusz resize poprawił się z 2 sekund do 1,3 milisekundy.
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
interface VirtualListProps<T> {
items: T[]
estimateSize: number
renderItem: (item: T, index: number) => React.ReactNode
}
export function VirtualList<T>({ items, estimateSize, renderItem }: VirtualListProps<T>) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => estimateSize,
overscan: 5, // Renderuj 5 dodatkowych elementów powyżej/poniżej viewport
})
return (
<div ref={parentRef} style={{ height: '100%', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{renderItem(items[virtualItem.index], virtualItem.index)}
</div>
))}
</div>
</div>
)
}TanStack Virtual używa transform: translateY() zamiast pozycjonowania top. Uruchamia to kompozycję GPU zamiast przeliczania layoutu, utrzymując przewijanie na poziomie 60fps nawet przy złożonym renderowaniu elementów.
Dynamiczne wysokości elementów z automatycznym pomiarem
Elementy o stałej wysokości upraszczają matematykę wirtualizacji, ale rzeczywiste aplikacje często wymagają zmiennych wysokości. TanStack Virtual mierzy elementy automatycznie używając ResizeObserver:
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef, useCallback } from 'react'
interface Message {
id: string
text: string
timestamp: number
}
export function ChatVirtualList({ messages }: { messages: Message[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: useCallback(() => 72, []), // Początkowe oszacowanie
measureElement: (element) => element.getBoundingClientRect().height,
})
return (
<div ref={parentRef} className="h-full overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
data-index={virtualItem.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ChatBubble message={messages[virtualItem.index]} />
</div>
))}
</div>
</div>
)
}Callback measureElement otrzymuje każdy wyrenderowany element i zwraca jego zmierzoną wysokość. TanStack Virtual cache'uje te pomiary i ponownie mierzy tylko gdy ResizeObserver wykryje zmiany.
React Virtuoso: Gotowy do produkcji od razu
React Virtuoso (wersja 4.18, wrzesień 2026) oferuje API wyższego poziomu, które automatycznie obsługuje przypadki brzegowe. Z 2,8 miliona pobrań tygodniowo na npm, stał się domyślnym wyborem dla zespołów chcących minimalnej konfiguracji.
import { Virtuoso } from 'react-virtuoso'
interface Product {
id: string
name: string
price: number
description: string
}
export function ProductList({ products }: { products: Product[] }) {
return (
<Virtuoso
data={products}
itemContent={(index, product) => (
<div className="p-4 border-b border-gray-200">
<h3 className="font-semibold">{product.name}</h3>
<p className="text-sm text-gray-600">{product.description}</p>
<span className="text-lg font-bold">${product.price}</span>
</div>
)}
style={{ height: '100%' }}
/>
)
}Virtuoso obsługuje zmienne wysokości, zgrupowane elementy i odwrotne przewijanie bez dodatkowej konfiguracji. Dla aplikacji czatowych lub przeglądarek logów wymagających dodawania elementów na początku przy zachowaniu pozycji przewijania, działa to płynnie:
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
import { useRef, useState, useCallback } from 'react'
export function ChatWithPrepend() {
const virtuosoRef = useRef<VirtuosoHandle>(null)
const [messages, setMessages] = useState<Message[]>(initialMessages)
const prependMessages = useCallback((newMessages: Message[]) => {
setMessages((prev) => [...newMessages, ...prev])
}, [])
return (
<Virtuoso
ref={virtuosoRef}
data={messages}
firstItemIndex={10000 - messages.length} // Offset wirtualnego indeksu
initialTopMostItemIndex={messages.length - 1}
followOutput="smooth" // Auto-scroll do nowych wiadomości
startReached={() => loadOlderMessages().then(prependMessages)}
itemContent={(index, message) => <ChatBubble message={message} />}
/>
)
}Właściwość firstItemIndex umożliwia dwukierunkowe nieskończone przewijanie. Virtuoso utrzymuje pozycję przewijania podczas dodawania elementów na początku poprzez dostosowywanie wirtualnych indeksów zamiast przesuwania pozycji DOM.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Porównanie wydajności: TanStack vs Virtuoso vs react-window
Benchmarki na średniej klasy laptopie z 2026 roku (M3 MacBook Air) ze 100 000 elementów:
| Biblioteka | Początkowy mount | Przewiń do środka | Zużycie pamięci |
|---|---|---|---|
| TanStack Virtual 3.13 | 4,5ms | 12ms | 2,1MB |
| react-virtuoso 4.18 | 6,2ms | 15ms | 3,4MB |
| react-window 1.8 | 3,8ms | 8ms | 1,8MB |
React-window pozostaje najszybszy dla elementów o stałej wysokości, ale brakuje mu dynamicznego pomiaru. TanStack Virtual oferuje najlepszą równowagę między wydajnością a funkcjami. Virtuoso priorytetyzuje doświadczenie programisty z automatyczną obsługą przypadków brzegowych.
Optymalizacja renderowania elementów przez memoizację
Wirtualizacja redukuje węzły DOM, ale nie może zapobiec kosztownym ponownym renderom wewnątrz elementów. React.memo staje się niezbędny:
import { memo } from 'react'
interface ListItemProps {
item: Product
onSelect: (id: string) => void
}
export const ListItem = memo(function ListItem({ item, onSelect }: ListItemProps) {
return (
<div
className="p-4 hover:bg-gray-50 cursor-pointer"
onClick={() => onSelect(item.id)}
>
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
)
}, (prevProps, nextProps) => {
// Własne porównanie: renderuj ponownie tylko gdy dane elementu się zmieniają
return prevProps.item.id === nextProps.item.id &&
prevProps.item.name === nextProps.item.name
})Funkcja własnego porównania zapobiega ponownym renderom gdy zmienia się tylko referencja, ale zawartość pozostaje identyczna, co jest częste przy wzorcach pobierania danych zwracających nowe referencje obiektów.
Obsługa przywracania pozycji przewijania
Użytkownicy oczekują zachowania pozycji przewijania przy nawigacji tam i z powrotem. History API w połączeniu ze stanem virtualizera umożliwia to:
import { useEffect, useRef } from 'react'
import { Virtualizer } from '@tanstack/react-virtual'
export function useScrollRestoration(
virtualizer: Virtualizer<HTMLDivElement, Element>,
storageKey: string
) {
const restoredRef = useRef(false)
// Przywróć przy montowaniu
useEffect(() => {
if (restoredRef.current) return
const saved = sessionStorage.getItem(storageKey)
if (saved) {
const { offset } = JSON.parse(saved)
virtualizer.scrollToOffset(offset, { align: 'start' })
}
restoredRef.current = true
}, [virtualizer, storageKey])
// Zapisz przed odmontowaniem
useEffect(() => {
return () => {
const offset = virtualizer.scrollOffset
sessionStorage.setItem(storageKey, JSON.stringify({ offset }))
}
}, [virtualizer, storageKey])
}Ten wzorzec przechowuje offset przewijania w sessionStorage, przetrwający nawigację między stronami, ale czyszczony przy zamknięciu karty.
Tematy rozmów kwalifikacyjnych: Pytania rekruterów o wirtualizację list
Rozmowy techniczne coraz częściej obejmują wiedzę o wirtualizacji. Typowe pytania i dobre odpowiedzi:
P: Dlaczego przeglądarki nie radzą sobie efektywnie ze 100k węzłów DOM? Pipeline renderowania przetwarza każdy węzeł podczas obliczania stylów i layoutu. Presja pamięciowa od obiektów węzłów, obliczonych stylów i drzew layoutu kumuluje się. Powyżej 10k węzłów pauzy garbage collection stają się zauważalne podczas przewijania.
P: Wyjaśnij windowing vs recycling. Windowing renderuje tylko widoczne elementy. Recycling ponownie używa istniejących węzłów DOM dla nowych danych gdy elementy przewijają się poza widok. TanStack Virtual i react-virtuoso używają windowing. Natywne frameworki mobilne (iOS UITableView, Android RecyclerView) używają recyclingu.
P: Jak zwirtualizowałbyś siatkę obrazów?
Użyj useVirtualizer z TanStack Virtual z wymiarami zarówno wierszy jak i kolumn. Zaimplementuj estimateSize dla obu osi. Dodaj lazy loading obrazów z IntersectionObserver. Rozważ CSS content-visibility: auto dla elementów blisko krawędzi viewport.
Więcej przygotowań do rozmów kwalifikacyjnych na tematy wydajności frontendowej można znaleźć w module pytań do rozmowy o testowaniu React, który obejmuje powiązane wzorce optymalizacji.
Typowe pułapki i jak ich unikać
Nieprawidłowa wysokość kontenera: Wirtualizacja wymaga kontenera przewijania o stałej wysokości. Używanie height: auto lub poleganie na wysokości zawartości psuje obliczenia pomiarów.
Brakujące klucze: React potrzebuje stabilnych kluczy dla elementów podlegających recyklingowi. Używanie indeksów tablicy powoduje błędy stanu gdy elementy zmieniają kolejność.
Zbyt niski overscan: Zerowy overscan powoduje widoczne puste obszary podczas szybkiego przewijania. Wartości między 3-10 równoważą wydajność i wizualną płynność.
Niezapamiętane callbacki: Przekazywanie funkcji inline jako propsów elementów wywołuje ponowne rendery przy każdym przewijaniu. Wyodrębnij handlery do useCallback lub zakresu komponentu.
Kluczowe wnioski dotyczące wirtualizacji list JavaScript
- Liczba węzłów DOM, nie rozmiar danych, określa wydajność przewijania. Wirtualizacja ogranicza węzły niezależnie od długości listy.
- TanStack Virtual 3.13 pasuje do aplikacji wymagających elastyczności frameworka i maksymalnej kontroli. Wydajność zimnego montowania poprawiła się o 35% w porównaniu z poprzednimi wersjami.
- react-virtuoso 4.18 obsługuje dynamiczne wysokości, grupowanie i dwukierunkowe przewijanie przy minimalnym kodzie. 2,8M pobrań tygodniowo odzwierciedla stabilność produkcyjną.
- react-window pozostaje opłacalny dla scenariuszy ze stałą wysokością gdzie rozmiar bundle ma znaczenie, choć aktywny rozwój ustał.
- Memoizuj elementy listy z funkcjami własnego porównania. Sprawdzanie równości referencji zawodzi gdy źródła danych tworzą nowe obiekty.
- Przechowuj offset przewijania w sessionStorage dla przywracania między nawigacjami. Offset virtualizera mapuje się bezpośrednio na pozycję piksela.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Znajdziesz błąd w Node.js / NestJS?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 7 września 2026
Udostępnij
Powiązane artykuły

Szybka Lista JavaScript w 2026: Windowing, Wirtualizacja i Optymalizacja Wydajności
Poznaj techniki windowing i wirtualizacji do renderowania tysięcy elementów w JavaScript. TanStack Virtual, react-virtuoso i najlepsze praktyki dla wydajnych list w 2026 roku.

NestJS i Redis w 2026: Cachowanie, Sesje i Pytania Rekrutacyjne
Kompleksowy przewodnik po integracji Redis z NestJS obejmujący cache-manager, zarządzanie sesjami i strategie invalidacji.

NestJS i MongoDB w 2026: Mongoose, Agregacje i Pytania Rekrutacyjne
Opanuj NestJS z MongoDB i Mongoose 9. Poznaj wzorce projektowania schematów, potoki agregacji i przygotuj się do rozmów kwalifikacyjnych z praktycznymi przykładami.