Fast JavaScript ListView 2026: Teknik Windowing, Virtualisasi dan Optimisasi Performa
Kuasai teknik virtualisasi list JavaScript dengan TanStack Virtual, react-virtuoso dan react-window. Pelajari windowing untuk merender 100.000+ item pada 60fps dengan contoh kode lengkap.

Merender ribuan item dalam JavaScript ListView tanpa virtualisasi menyebabkan browser membuat ribuan node DOM, yang mengakibatkan waktu render awal lambat, scrolling tersendat, dan konsumsi memori yang dapat membuat perangkat mobile crash. Implementasi Fast JavaScript ListView pada tahun 2026 menyelesaikan masalah ini melalui windowing: hanya merender 10-20 item yang terlihat di viewport sambil mensimulasikan area scrollable yang berisi semua item.
Windowing hanya merender item yang terlihat ditambah buffer kecil (overscan), mengurangi node DOM dari 10.000 menjadi sekitar 20 terlepas dari ukuran list.
Mengapa Performa Browser Menurun dengan List Besar
Setiap node DOM mengkonsumsi memori dan memerlukan browser untuk melacak posisinya selama perhitungan layout. List dengan 10.000 item dengan markup yang cukup kompleks (avatar, dua text span, tombol) menciptakan 40.000+ node DOM. Rendering engine Chrome menghitung ulang style dan posisi untuk semua node tersebut pada setiap event scroll.
Hasilnya: render awal membutuhkan 2-4 detik, scrolling turun ke 15fps, dan penggunaan memori naik hingga 500MB+. Pada mobile Safari, tab sering crash secara langsung.
Virtualisasi memperbaiki ini dengan menjaga jumlah node DOM konstan. Baik array data berisi 100 atau 100.000 item, DOM yang dirender tetap sekitar 20-30 node, menjaga memori stabil dan performa scroll pada 60fps.
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<HTMLDivElement>(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 (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const item = items[virtualRow.index]
return (
<div
key={item.id}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<div className="flex items-center gap-3 p-3 border-b">
<div className="w-10 h-10 rounded-full bg-gray-200" />
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-gray-500">{item.email}</p>
</div>
</div>
</div>
)
})}
</div>
</div>
)
}Implementasi ini hanya merender baris yang terlihat ditambah buffer overscan. Container luar mengatur total tinggi scrollable, membuat scrollbar berperilaku seolah-olah semua item ada dalam DOM.
TanStack Virtual: Pendekatan Headless untuk 2026
TanStack Virtual menyediakan primitif virtualisasi yang framework-agnostic. Berbeda dengan react-window atau react-virtuoso, library ini tidak memberikan komponen pre-built, hanya hooks yang menghitung item mana yang harus dirender dan di mana posisinya. Arsitektur headless ini memberikan kontrol penuh atas styling dan struktur DOM.
Versi saat ini (3.13) mendukung React, Vue, Solid, Svelte, dan vanilla JavaScript. Opsi konfigurasi utama:
| Opsi | Tujuan | Default |
|---|---|---|
count | Total jumlah item | required |
estimateSize | Fungsi yang mengembalikan estimasi tinggi/lebar item | required |
overscan | Item tambahan yang dirender di luar viewport | 1 |
getScrollElement | Mengembalikan container scrollable | required |
measureElement | Fungsi pengukuran kustom untuk ukuran dinamis | auto |
Nilai overscan secara langsung mempengaruhi kelancaran scroll. Mengaturnya terlalu rendah (0-2) menyebabkan flash putih saat scrolling cepat. Mengaturnya terlalu tinggi (20+) meniadakan manfaat virtualisasi. Untuk sebagian besar aplikasi, 5-10 memberikan keseimbangan optimal.
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<HTMLDivElement>(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 (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => {
const message = messages[virtualRow.index]
return (
<div
key={message.id}
data-index={virtualRow.index}
ref={virtualizer.measureElement} // enables auto-measurement
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<div className="p-4 border-b">
<p className="text-sm text-gray-500">
{new Date(message.timestamp).toLocaleTimeString()}
</p>
<p className="mt-1">{message.content}</p>
</div>
</div>
)
})}
</div>
</div>
)
}Ref measureElement memungkinkan pengukuran tinggi otomatis setelah render. Ini menangani panjang konten yang bervariasi tanpa memerlukan perhitungan tinggi manual.
react-virtuoso: Alternatif Kaya Fitur
react-virtuoso mengambil pendekatan sebaliknya: library komponen batteries-included. Di mana TanStack Virtual memerlukan pembangunan scroll container dan logika positioning, react-virtuoso menangani ini secara internal.
Tradeoff-nya adalah ukuran bundle (37KB vs 12KB untuk TanStack Virtual) tetapi jauh lebih sedikit boilerplate untuk pola umum seperti grouped list, sticky header, dan interface chat dengan reverse scrolling.
import { Virtuoso } from 'react-virtuoso'
interface User {
id: string
name: string
department: string
}
export function UserDirectory({ users }: { users: User[] }) {
return (
<Virtuoso
style={{ height: '600px' }}
data={users}
itemContent={(index, user) => (
<div className="flex items-center gap-3 p-4 border-b hover:bg-gray-50">
<div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
{user.name.charAt(0)}
</div>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-gray-500">{user.department}</p>
</div>
</div>
)}
/>
)
}Ini mencapai hasil virtualisasi yang sama dengan kode lebih sedikit. react-virtuoso secara otomatis mengukur item, menangani tinggi dinamis, dan menyediakan persistensi posisi scroll.
Untuk persiapan interview tentang performa React, memahami kapan memilih setiap library menunjukkan kesadaran arsitektural. TanStack Virtual cocok untuk aplikasi yang sudah menggunakan TanStack Table atau memerlukan kode framework-agnostic. react-virtuoso cocok untuk tim yang memprioritaskan kecepatan development daripada ukuran bundle.
Siap menguasai wawancara Node.js / NestJS Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Kesalahan Umum dan Solusinya
Scroll Position Melompat dengan Gambar
Gambar dimuat secara asynchronous, mengubah tinggi baris setelah pengukuran awal. Virtualizer tidak dapat memprediksi perubahan ini, menyebabkan konten di bawahnya bergeser.
Solusinya: reservasi ruang untuk gambar menggunakan aspect-ratio atau dimensi tetap.
interface ImageItem {
id: string
src: string
title: string
}
export function ImageListItem({ item }: { item: ImageItem }) {
return (
<div className="flex gap-4 p-4 border-b">
{/* Reserve 80x80px space regardless of load state */}
<div className="w-20 h-20 flex-shrink-0 bg-gray-100">
<img
src={item.src}
alt={item.title}
className="w-full h-full object-cover"
loading="lazy"
/>
</div>
<p className="font-medium">{item.title}</p>
</div>
)
}State Hilang Saat Scroll
Item yang divirtualisasi unmount ketika scroll keluar dari tampilan. State lokal apapun (input form, accordion yang terbuka) menghilang.
Solusinya: angkat state ke komponen parent atau library state management, dengan key berdasarkan ID item.
import { useState, useCallback } 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<Set<string>>(new Set())
const parentRef = useRef<HTMLDivElement>(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 (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const faq = faqs[virtualRow.index]
const isExpanded = expandedIds.has(faq.id)
return (
<div
key={faq.id}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<button
onClick={() => toggleExpanded(faq.id)}
className="w-full text-left p-4 border-b"
>
<p className="font-medium">{faq.question}</p>
{isExpanded && (
<p className="mt-2 text-gray-600">{faq.answer}</p>
)}
</button>
</div>
)
})}
</div>
</div>
)
}Key Tidak Stabil Menyebabkan Re-render
Menggunakan indeks array sebagai key berfungsi sampai list diurutkan ulang. Item menerima state yang salah, menyebabkan glitch visual dan render yang terbuang.
Selalu gunakan identifier unik dan stabil dari data itu sendiri:
// Correct: stable key from data
{virtualizer.getVirtualItems().map((virtualRow) => {
const item = items[virtualRow.index]
return <div key={item.id}>...</div> // id from database/API
})}
// Wrong: index changes when list reorders
{virtualizer.getVirtualItems().map((virtualRow, index) => {
return <div key={index}>...</div> // causes bugs on reorder
})}Perbandingan Library untuk Proyek 2026
Memilih library virtualisasi yang tepat tergantung pada kebutuhan proyek. Perbandingan ini mencerminkan ekosistem 2026:
| Kriteria | TanStack Virtual | react-virtuoso | react-window |
|---|---|---|---|
| Ukuran bundle | 12KB | 37KB | 6KB |
| Tinggi dinamis | Setup manual | Otomatis | Tidak didukung |
| TypeScript | Native | Native | package @types |
| Dukungan framework | React, Vue, Solid, Svelte | React saja | React saja |
| Maintenance | Aktif (2026) | Aktif (2026) | Mode maintenance |
| Learning curve | Lebih tinggi | Lebih rendah | Paling rendah |
react-window tetap viable untuk list fixed-height sederhana di mana ukuran bundle penting. Namun, status maintenance-mode-nya dan kurangnya dukungan tinggi dinamis membuatnya tidak cocok untuk proyek baru dengan kebutuhan kompleks.
Untuk membangun aplikasi React yang performant, memahami tradeoff virtualisasi menunjukkan kesadaran performa yang dicari interviewer.
Mengukur Peningkatan Performa
Mengkuantifikasi manfaat virtualisasi memerlukan metodologi pengukuran yang konsisten. Panel Performance Chrome DevTools menyediakan metrik yang relevan.
Pengukuran kunci sebelum/sesudah virtualisasi:
- First Contentful Paint (FCP): Waktu sampai konten pertama dirender. Virtualisasi mengurangi ini dengan membatasi pembuatan DOM awal.
- Total Blocking Time (TBT): Blocking main thread selama load. Lebih sedikit node DOM berarti lebih sedikit perhitungan style/layout.
- Penggunaan memori: Snapshot heap size di panel Memory. Harus tetap konstan terlepas dari panjang list.
- Scroll fps: Rekam trace Performance saat scrolling. Frame rate harus tetap pada 60fps.
import { useEffect, useRef } from 'react'
export function useScrollFPS(containerRef: React.RefObject<HTMLElement>) {
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])
}Hook ini mencatat scroll FPS selama development. Aplikasi production harus menggunakan tool observability seperti Sentry Performance atau telemetry kustom.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Poin Penting untuk Performa List JavaScript
- Virtualisasi mengurangi node DOM dari ribuan menjadi sekitar 20, menjaga memori dan performa scroll konstan terlepas dari ukuran data
- TanStack Virtual (12KB) menyediakan primitif headless untuk kontrol maksimal di React, Vue, Solid, dan Svelte
- react-virtuoso (37KB) menawarkan komponen batteries-included dengan penanganan tinggi dinamis otomatis
- react-window tetap dalam mode maintenance sejak 2022 dan tidak mendukung tinggi dinamis
- Atur overscan antara 5-10 untuk kelancaran scroll optimal tanpa meniadakan manfaat virtualisasi
- Reservasi dimensi gambar untuk mencegah lompatan posisi scroll selama loading asynchronous
- Angkat state expandable/editable ke komponen parent karena baris yang divirtualisasi unmount saat scroll
- Gunakan key stabil dari ID data, bukan indeks array, untuk mencegah ketidakcocokan state saat reorder
- Ukur FCP, TBT, memori, dan scroll FPS sebelum/sesudah implementasi untuk mengkuantifikasi peningkatan
Bisakah kamu menemukan bug di Node.js / NestJS?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 15 September 2026
Tag
Bagikan
Artikel terkait

High Performance JavaScript ListView 2026: Teknik Virtualisasi dan Optimasi
Panduan lengkap teknik virtualisasi list JavaScript untuk performa tinggi di tahun 2026. Pelajari windowing, lazy loading, dan optimasi rendering untuk aplikasi skala besar.

Microservices dengan NestJS di 2026: Arsitektur, gRPC, dan Pertanyaan Wawancara
Panduan lengkap arsitektur microservices NestJS dengan gRPC: transport layer, Protocol Buffers, streaming patterns, dan pertanyaan wawancara untuk backend engineer di 2026.

NestJS + Prisma: stack backend modern untuk Node.js
Panduan lengkap untuk membangun API backend modern dengan NestJS dan Prisma. Setup, model, service, transaksi, dan praktik terbaik dijelaskan.