2026'da Hızlı JavaScript ListView: Windowing, Sanallaştırma ve Performans Optimizasyonu

JavaScript'te binlerce öğeyi render etmek için windowing ve sanallaştırma tekniklerini keşfedin. TanStack Virtual, react-virtuoso ve 2026'da verimli listeler için en iyi uygulamalar.

2026'da Hızlı JavaScript ListView: Windowing, Sanallaştırma ve Performans Optimizasyonu

Sanallaştırma olmadan bir JavaScript ListView'de binlerce öğeyi render etmek, tarayıcının binlerce DOM düğümü oluşturmasına neden olur ve bu da yavaş ilk render süreleri, takılan kaydırma ve mobil cihazları çökertebilecek bellek tüketimine yol açar. 2026'da hızlı JavaScript ListView uygulamaları bu sorunu windowing ile çözer: viewport'ta görünen yalnızca 10-20 öğeyi render ederken tüm öğeleri içeren kaydırılabilir bir alan simüle eder.

Windowing Tek Cümlede

Windowing yalnızca görünür öğeleri ve küçük bir tampon (overscan) render eder, DOM düğümlerini liste boyutundan bağımsız olarak 10.000'den yaklaşık 20'ye düşürür.

Büyük Listelerde Tarayıcı Performansı Neden Düşer

Her DOM düğümü bellek tüketir ve tarayıcının düzen hesaplamaları sırasında konumunu takip etmesini gerektirir. Orta düzeyde karmaşık işaretlemeye sahip 10.000 öğelik bir liste (avatar, iki metin spanı, bir düğme) 40.000'den fazla DOM düğümü oluşturur. Chrome'un render motoru, her kaydırma olayında hepsi için stilleri ve konumları yeniden hesaplar.

Sonuç: ilk render 2-4 saniye sürer, kaydırma 15fps'ye düşer ve bellek kullanımı 500MB'ı aşar. Mobil Safari'de sekme genellikle tamamen çöker.

Sanallaştırma, sabit bir DOM düğüm sayısı tutarak bu sorunu çözer. Veri dizisi 100 veya 100.000 öğe içerse de, render edilen DOM yaklaşık 20-30 düğümde kalır, bellek tüketimini sabit tutar ve kaydırma performansını 60fps'de korur.

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>
  )
}

Bu uygulama yalnızca görünür satırları ve overscan tamponunu render eder. Dış konteyner toplam kaydırılabilir yüksekliği ayarlar ve kaydırma çubuğunun tüm öğeler DOM'da varmış gibi davranmasını sağlar.

TanStack Virtual: 2026 İçin Başsız Yaklaşım

TanStack Virtual framework-agnostik sanallaştırma primitifleri sağlar. react-window veya react-virtuoso'dan farklı olarak, önceden oluşturulmuş bileşenler sunmaz - yalnızca hangi öğelerin render edileceğini ve nereye konumlandırılacağını hesaplayan hook'lar sunar. Bu başsız mimari, stiller ve DOM yapısı üzerinde tam kontrol anlamına gelir.

Mevcut sürüm (3.13) React, Vue, Solid, Svelte ve vanilla JavaScript'i destekler. Temel yapılandırma seçenekleri:

SeçenekAmaçVarsayılan
countToplam öğe sayısızorunlu
estimateSizeTahmini öğe yüksekliği/genişliği döndüren fonksiyonzorunlu
overscanViewport dışında render edilen ekstra öğeler1
getScrollElementKaydırılabilir konteyneri döndürürzorunlu
measureElementDinamik boyutlar için özel ölçüm fonksiyonuotomatik

overscan değeri doğrudan kaydırma akıcılığını etkiler. Çok düşük ayarlamak (0-2) hızlı kaydırma sırasında beyaz yanıp sönmelere neden olur. Çok yüksek ayarlamak (20+) sanallaştırma faydalarını ortadan kaldırır. Çoğu uygulama için 5-10 optimal dengeyi sağlar.

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>
  )
}

measureElement ref'i render sonrasında otomatik yükseklik ölçümünü etkinleştirir. Bu, manuel yükseklik hesaplaması gerektirmeden değişken içerik uzunluklarını yönetir.

react-virtuoso: Zengin Özellikli Alternatif

react-virtuoso tam tersi yaklaşımı benimser: her şey dahil bir bileşen kütüphanesi. TanStack Virtual kaydırma konteyneri ve konumlandırma mantığı oluşturmayı gerektirirken, react-virtuoso bunları dahili olarak yönetir.

Takas, paket boyutudur (TanStack Virtual için 37KB vs 12KB) ancak gruplandırılmış listeler, yapışkan başlıklar ve ters kaydırmalı sohbet arayüzleri gibi yaygın kalıplar için önemli ölçüde daha az şablon kod gerektirir.

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>
      )}
    />
  )
}

Bu, daha az kodla aynı sanallaştırma sonucunu elde eder. react-virtuoso otomatik olarak öğeleri ölçer, dinamik yükseklikleri yönetir ve kaydırma pozisyonu kalıcılığı sağlar.

React performansı üzerine mülakat hazırlığı için her kütüphanenin ne zaman seçileceğini anlamak mimari farkındalığı gösterir. TanStack Virtual, zaten TanStack Table kullanan veya framework-agnostik kod gerektiren uygulamalara uygundur. react-virtuoso, geliştirme hızını paket boyutuna tercih eden ekiplere uygundur.

Node.js / NestJS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Yaygın Tuzaklar ve Çözümler

Görsellerle Kaydırma Pozisyonu Atlamaları

Görseller asenkron olarak yüklenir ve ilk ölçümden sonra satır yüksekliğini değiştirir. Sanallaştırıcı bu değişikliği tahmin edemez ve alttaki içeriğin kaymasına neden olur.

Çözüm: aspect-ratio veya sabit boyutlar kullanarak görseller için yer ayırmak.

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>
  )
}

Kaydırma Sırasında Durum Kaybı

Sanallaştırılmış öğeler görünüm alanından çıkınca unmount olur. Herhangi bir yerel durum (form girdileri, genişletilmiş akordeonlar) kaybolur.

Çözüm: durumu üst bileşene veya öğe ID'sine göre anahtarlanmış bir durum yönetimi kütüphanesine taşımak.

ExpandableList.tsxtypescript
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<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>
  )
}

Yeniden Renderlara Neden Olan Kararsız Anahtarlar

Dizi indekslerini anahtar olarak kullanmak, liste yeniden sıralanana kadar çalışır. Öğeler yanlış durum alır ve görsel hatalara ve boşa harcanan renderlara neden olur.

Her zaman verinin kendisinden kararlı, benzersiz tanımlayıcılar kullanılmalıdır:

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
})}

2026 Projeleri İçin Kütüphane Karşılaştırması

Doğru sanallaştırma kütüphanesini seçmek proje gereksinimlerine bağlıdır. Bu karşılaştırma 2026 ekosistemini yansıtır:

KriterTanStack Virtualreact-virtuosoreact-window
Paket boyutu12KB37KB6KB
Dinamik yüksekliklerManuel kurulumOtomatikDesteklenmiyor
TypeScriptYerelYerel@types paketi
Framework desteğiReact, Vue, Solid, SvelteYalnızca ReactYalnızca React
BakımAktif (2026)Aktif (2026)Bakım modu
Öğrenme eğrisiDaha yüksekDaha düşükEn düşük

react-window, paket boyutunun önemli olduğu basit sabit yükseklikli listeler için hala geçerlidir. Ancak bakım modu durumu ve dinamik yükseklik desteği eksikliği, karmaşık gereksinimleri olan yeni projeler için uygun değildir.

Performanslı React uygulamaları oluşturmak için sanallaştırma takaslarını anlamak, mülakatçıların aradığı performans farkındalığını gösterir.

Performans İyileştirmelerini Ölçme

Sanallaştırma faydalarını ölçmek tutarlı bir ölçüm metodolojisi gerektirir. Chrome DevTools Performance paneli ilgili metrikleri sağlar.

Sanallaştırma öncesi/sonrası temel ölçümler:

  • First Contentful Paint (FCP): İlk içerik render edilene kadar geçen süre. Sanallaştırma, ilk DOM oluşturmayı sınırlayarak bunu azaltır.
  • Total Blocking Time (TBT): Yükleme sırasında ana thread engelleme. Daha az DOM düğümü, daha az stil/düzen hesaplaması anlamına gelir.
  • Bellek kullanımı: Memory panelinde heap boyutu snapshot'ı. Liste uzunluğundan bağımsız olarak sabit kalmalıdır.
  • Kaydırma fps: Kaydırma sırasında Performance izi kaydedin. Kare hızı 60fps'de kalmalıdır.
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])
}

Bu hook geliştirme sırasında kaydırma FPS'ini loglar. Üretim uygulamaları Sentry Performance veya özel telemetri gibi gözlemlenebilirlik araçları kullanmalıdır.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

JavaScript Liste Performansı İçin Temel Çıkarımlar

  • Sanallaştırma, DOM düğümlerini binlerden yaklaşık 20'ye düşürür ve veri boyutundan bağımsız olarak bellek ve kaydırma performansını sabit tutar
  • TanStack Virtual (12KB), React, Vue, Solid ve Svelte'de maksimum kontrol için başsız primitifler sağlar
  • react-virtuoso (37KB), otomatik dinamik yükseklik yönetimi ile her şey dahil bileşenler sunar
  • react-window 2022'den beri bakım modunda ve dinamik yükseklikleri desteklemiyor
  • Overscan'i 5-10 arasında ayarlamak, sanallaştırma faydalarını ortadan kaldırmadan optimal kaydırma akıcılığı sağlar
  • Görsel boyutlarını rezerve etmek, asenkron yükleme sırasında kaydırma pozisyonu atlamalarını önler
  • Genişletilebilir/düzenlenebilir durumu üst bileşene taşıyın çünkü sanallaştırılmış satırlar kaydırma sırasında unmount olur
  • Yeniden sıralamada durum uyuşmazlıklarını önlemek için dizi indeksleri yerine veri ID'lerinden kararlı anahtarlar kullanın
  • İyileştirmeleri ölçmek için uygulama öncesi/sonrası FCP, TBT, bellek ve kaydırma FPS'ini ölçün
Günün meydan okuması

Node.js / NestJS kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

15 Eylül 2026 tarihinde güncellendi

Paylaş

İlgili makaleler