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.

Virtualisasi list JavaScript dengan teknik windowing untuk performa scrolling cepat

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 dalam Satu Kalimat

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.

VirtualizedList.tsxtypescript
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:

OpsiTujuanDefault
countTotal jumlah itemrequired
estimateSizeFungsi yang mengembalikan estimasi tinggi/lebar itemrequired
overscanItem tambahan yang dirender di luar viewport1
getScrollElementMengembalikan container scrollablerequired
measureElementFungsi pengukuran kustom untuk ukuran dinamisauto

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.

DynamicHeightList.tsxtypescript
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.

VirtuosoList.tsxtypescript
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.

ImageListItem.tsxtypescript
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.

ExpandableList.tsxtypescript
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:

typescript
// 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:

KriteriaTanStack Virtualreact-virtuosoreact-window
Ukuran bundle12KB37KB6KB
Tinggi dinamisSetup manualOtomatisTidak didukung
TypeScriptNativeNativepackage @types
Dukungan frameworkReact, Vue, Solid, SvelteReact sajaReact saja
MaintenanceAktif (2026)Aktif (2026)Mode maintenance
Learning curveLebih tinggiLebih rendahPaling 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.
PerformanceMonitor.tsxtypescript
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
Tantangan harian

Bisakah kamu menemukan bug di Node.js / NestJS?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri 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

#javascript
#performance
#virtualization
#react
#typescript

Bagikan

Artikel terkait