Uzun Listeler İçin Virtualization: Ne Zaman Zorunlu Olur?
Uzun listelerde virtualization ne zaman zorunlu hale gelir, ne zaman erken optimizasyondur? DOM maliyetini ölçmek, windowing kütüphanelerini karşılaştırmak ve erişilebilirlik dengesini kurmak için karar rehberi.
Bir listeyi sayfaya dökerken çoğu geliştirici, öğe sayısı birkaç yüzü geçene kadar performans sorunuyla karşılaşmaz. Sorun yavaş yavaş birikir: önce kaydırma takılmaya başlar, sonra sayfa gecikmeli yanıt verir, en son tarayıcı bellek uyarısı fırlatır. O noktada "virtualization ekleyelim" kararı gelir; ama bu kararın ne zaman doğru, ne zaman erken, ne zaman da yanlış olduğunu anlamak, uygulama mimarisini ve bakım maliyetini doğrudan etkiler.
Virtualization (windowing olarak da bilinir), ekranda yalnızca görünen öğeleri DOM'a ekleyip görünüm dışına çıkanları kaldıran bir tekniktir. Fikir basit görünür; ama uygulamanın getirdiği kısıtlamalar, erişilebilirlik ödünleri ve kütüphane seçiminin yarattığı bağımlılıklar, bu çözümü "her uzun liste için standart" yapmaz.
Karar vermeden önce sorunun gerçekten DOM'dan kaynaklandığını doğrulamak gerekir. Yavaş kaydırma her zaman fazla DOM öğesinin işareti değildir; ağır hesaplama, gereksiz re-render veya büyük resim dosyaları da aynı semptomu üretebilir.
DOM Maliyeti: Her Öğe Hafızada Ne Kadar Yer Tutar?
Tarayıcılar her DOM düğümü için bellek ayırır, stil hesaplar ve layout ağacına ekler. Sade bir <li> bile tarayıcı açısından birkaç nesne, onlarca CSS özelliği ve birkaç kilobayt bellek anlamına gelir. İçinde resim, buton ve metin olan bir kart bileşeni ise bunun on katına kolayca ulaşır.
Genel gözlem şudur: 200-300 öğeye kadar çoğu modern tarayıcı sorunsuz çalışır. 500-1000 arası, öğelerin karmaşıklığına bağlı olarak kaydırma sürüncemesine yol açabilir. 1000 öğenin üstünde, sade öğeler bile ilk render süresini fark edilir biçimde uzatır; karmaşık bileşenlerde bu etki çok daha erken başlar.
Bu sayılar mutlak eşikler değildir. 500 öğeli sade bir metin listesi, 100 öğeli video kart listesinden çok daha az sorun çıkarabilir. Sadece öğe sayısına bakmak yanıltıcıdır; her öğenin bileşen ağacı derinliği, event listener sayısı ve içerdiği medya içeriği de maliyet belirler. Bir liste öğesinde birden fazla useEffect çağrısı varsa, 1000 öğe mount edildiğinde 1000 effect çalışır; unmount sırasında temizlik fonksiyonları devreye girer ve bu yük görünmez biçimde birikerek ölçülmesi güç bir yavaşlamaya dönüşebilir.
Kaç Öğede Sorun Başlar? Ölçüm Önce Gelir
Virtualization kararının sağlıklı temeli, tarayıcı geliştirici araçlarının Performance sekmesidir. Kaydırma sırasında bir kayıt alırsanız, uzun "Recalculate Style" veya "Layout" blokları DOM'un maliyetini gösterir. Bu bloklar kısaysa ve asıl süre "Scripting"te geçiyorsa, sorun kalabalık DOM değil, bileşen içindeki hesaplamadır.
İzlenecek iki sinyal: ilk render süresi ve kaydırma sırasında frame düşüşü. Kaydırma 60 fps'in altına düşüyorsa ve nedeni Style veya Layout hesabıyla ilgiliyse, DOM boyutu gerçek bir sorun kaynağıdır. Yalnızca "Scripting" bloğu uzunsa, render maliyetini azaltmak için önce bileşen memoization stratejisini ve bileşen yapısını gözden geçirmek daha uygun bir başlangıç noktası olur.
Memory sekmesinde heap snapshot alarak listenin bellek baskısını da görebilirsiniz. 5000 öğeli liste için DOM ağırlıklı bellek kullanımı fazlaysa ve hedef cihaz için bu baskı kritikse, virtualization gerekçesi güçlüdür.
Mobil cihazlarda eşikler masaüstüne göre çok daha düşüktür. Zayıf CPU ve sınırlı bellek kombinasyonu, masaüstünde sorunsuz çalışan bir listeyi mobilde yavaşlatabilir. Ölçümü hedef cihazda ya da DevTools'un CPU kısıtlama modu açıkken yapmak, çok daha gerçekçi bir tablo verir.
Windowing Mantığı Nasıl Çalışır?
Bir virtualizer, kullanıcının kaydırma konumunu takip eder ve yalnızca viewport içindeki (artı küçük bir overscan buffer) öğeleri DOM'a ekler. Kullanıcı aşağı kaydırdıkça üstteki öğeler DOM'dan çıkar, alttaki yeniler eklenir. Dışarıdan bakıldığında liste sürekli varmış gibi görünür; içeride yalnızca birkaç düzine öğe DOM'da bulunur.
Mekanizmanın çalışması için her öğenin bilinen ya da tahmin edilebilir bir yüksekliğe sahip olması gerekir. Sabit yükseklik en basit ve performanslı halidir. Dinamik yükseklik (içeriğe göre büyüyen kartlar, farklı uzunlukta metin blokları) teknik olarak mümkündür; ama her öğenin ölçülmesi zorunlu hale gelir ve hem uygulama karmaşıklığı hem de başlangıç maliyeti artar.
Ölçüm sorunları kaydırma sırasında sıçramalara yol açar. Geç yüklenen resimler, font değişimleri veya tarayıcının ilk ölçümden farklı bir yükseklik hesaplaması, virtualizer'ın konumlandırmasını bozabilir. Bu sıçramalar, sade DOM listesinin yavaşlığından çok daha kötü bir deneyim oluşturabilir; dinamik içerikle çalışırken bu riski baştan hesaba katmak gerekir.
Kütüphane Seçimi: react-window, TanStack Virtual ve Alternatifleri
React ekosisteminde en yaygın iki seçenek react-window ve TanStack Virtual'dır. İkisi de windowing mantığını uygular; ama tasarım öncelikleri farklıdır.
react-window, sade ve hafif bir API sunar. Sabit yükseklikli listeler için FixedSizeList, değişken yükseklik için VariableSizeList bileşenleri vardır. Bundle boyutu küçüktür ve sade senaryolarda az yapılandırma gerektirir. Grid ve tablo senaryolarında ek paket olan react-virtualized'a geçmek gerekebilir; bu da bağımlılık zincirini uzatır.
TanStack Virtual, framework bağımsız bir çekirdek üzerine kuruludur; React, Vue, Svelte ve Solid için adaptörler sunar. "Headless" tasarımı DOM'u sizin yönetmenizi gerektirir, ama tam kontrol sağlar. Dinamik boyut, yatay liste, grid ve infinite scroll için daha olgun altyapısı vardır. react-window'a göre kurulum daha fazla kod gerektirir; uzun vadeli esneklik ve özelleştirme açısından bu ek kod çoğu projede karşılığını verir.
Seçimi belirleyen üç soru: Öğelerin yüksekliği sabit mi değişken mi? Uygulama ilerleyen dönemde başka bir framework'e geçmeyi planlıyor mu? Infinite scroll ya da sanal grid gerekiyor mu? Sabit yükseklik ve sade senaryo için react-window yeterlidir; dinamik içerik ve geniş kontrol için TanStack Virtual daha sağlam bir temel oluşturur. Çok bileşenli uygulamalarda render maliyetini azaltmak için alınan kararlarla da bir denge kurmak gerekir; virtualization her öğenin yeniden mount edilmesini içerdiğinden, bileşen ağırlaştıkça windowing'in kaydırma maliyeti de artar.
Erişilebilirlik Dengesi: Göz Ardı Edilen Maliyet
Virtualizer, DOM'dan çıkardığı öğeleri ekran okuyucudan da gizler. Bir liste "1000 öğe" içeriyorsa ama DOM'da yalnızca 20'si varsa, ekran okuyucu 20 öğe görür ve "liste 20 öğe içeriyor" diye duyurur. Bu, görme engelli kullanıcılar için hatalı bir bağlam oluşturur. Bazı kütüphaneler bu soruna kısmi çözümler sunar; aria-rowcount ve aria-rowindex gibi niteliklerle gerçek öğe sayısını bildirebilirsiniz, ama klavye gezinmesi ve odak yönetimi çok daha karmaşık bir yapı gerektirir.
Sanal bir listeye klavyeyle erişmek de zorlaşır. Kullanıcı Tab veya ok tuşlarıyla DOM'da olmayan bir öğeye ulaşmaya çalıştığında, odak beklenmedik biçimde kayar ya da tamamen kaybolur. Bu sorunu çözmek için odak yönetimini virtualizer'a özel olarak yazmak gerekir; standart HTML liste davranışını almak artık mümkün değildir. Uygulamanız erişilebilirlik uyumluluğuna (WCAG 2.1 AA gibi) tabi tutuluyorsa, bu ek geliştirme maliyetini baştan planlamak kaçınılmazdır.
Erişilebilirlik yükü, virtualization kararını tek başına engellemez; ama denkleme dahil edilmesi gereken gerçek bir maliyettir. Liste yalnızca görsel kullanıcılar için tasarlanmışsa ve öğe sayısı gerçekten büyükse, bu maliyet kabul edilebilir. Ancak performans kararlarını yayına çıkmadan bütünsel değerlendirmek gerektiğinde, erişilebilirlik etkisini "sonra hallederiz" kategorisine bırakmak sık yapılan bir hatadır.
Virtualization'ın Gereksiz Kaldığı Durumlar
Her uzun liste virtualization gerektirmez. Karar vermeden önce iki soruyu yanıtlamak yeterlidir: Liste gerçekten hepsini aynı anda gösteriyor mu? Ve öğe sayısı, hedef cihazda ölçülebilir bir sorun yaratıyor mu?
Hayır.
Sonuç olumsuzsa, alternatifler çok daha az maliyetlidir. Sayfalandırma (pagination), görüntülenen öğe sayısını sabit tutar ve DOM hiçbir zaman şişmez. Arayüz açısından alışılageldik bir deneyim sunar ve erişilebilirlik sorunları doğurmaz. Infinite scroll, verilen an yalnızca bir bölümü DOM'a ekler; sonraki batch'ler kullanıcı kaydırdıkça eklenir, eski batch'ler DOM'da kalır. Bu yaklaşım, virtualization kadar agresif bir DOM temizliği yapmaz ama öğe sayısı binlere ulaşmıyorsa yeterlidir. DOM'da kalan öğeler biriktiğinde başlangıç noktasına döner; çok uzun oturumlarda yine şişme sorunu oluşur.
Başka bir senaryo: veriler zaten sunucu tarafında filtreleniyor ya da arama/sıralama sonucu küçük alt kümeler gösteriliyorsa, DOM'da hiçbir zaman yüzlerce öğe birikmez. Bu durumda virtualization eklemek gereksiz karmaşıklık yaratır ve bakım yükünü artırır. Kodun karmaşıklığı ile sayfa sadelığı arasındaki dengeyi düşündüğünüzde, doğru soru şudur: Bu karmaşıklığı gerçekten ölçülen bir soruna karşı ekliyor muyum, yoksa "ileride sorun çıkarabilir" varsayımıyla mı?
Tablo ve grid senaryolarında virtualization kararı daha sık doğru çıkar. Binlerce satır ve onlarca sütun içeren bir veri tablosunda hem satır hem sütun virtualization'ı DOM'u yönetilebilir tutar. Ama 50 satırlı bir tablo için bu yük fazladır; standart HTML tablosu çok daha az kırılgandır. Tasarım sistemi bileşenlerinin performans etkisi değerlendirildiğinde, veri tablosu bileşenlerinin içine gömülü virtualization özellikle dikkat ister; genellikle varsayılan olarak kapalı gelir ve bilinçli etkinleştirilmesi gerekir.
Karar Çerçevesi: Listeyi Tanıyarak Seçim Yap
Virtualization kararını şu sıraya göre yürütmek, hem erken optimizasyondan hem de geç müdahaleden korur.
İlk adım ölçümdür: DevTools Performance sekmesinde kaydırma kaydı al, Layout ve Style bloklarının toplam kaydırma süresine oranını gör. Belirgin bir sorun yoksa devam etme.
İkinci adım öğe yapısıdır: Bileşen başına event listener, iç içe geçmiş bileşen sayısı ve medya içeriği kontrol edilir. Ağır bileşenler, öğe sayısından önce sorun yaratır ve bu durumda gereksiz hesaplama sarmalayıcılarını kaldırmak ya da bileşeni basitleştirmek daha etkili olur.
Üçüncü adım liste büyüklüğü projeksiyonudur: Verinin maksimum boyutu nedir? Kullanıcı asla 200'den fazla öğeyle mi çalışır, yoksa veri seti zamanla büyüyebilir mi? Büyüme öngörülüyorsa ve öğe yapısı karmaşıksa, virtualization o noktada doğal bir seçimdir. Performans borcunun kademeli birikmesi en çok bu tür listelerde görülür; kısa vadede sorunsuz görünen liste, üretime geçince binlerce öğeye ulaşır ve müdahale çok daha maliyetli hale gelir.
Dördüncü adım erişilebilirlik kapsamıdır: Uygulama WCAG uyumluluğu gerektiriyor mu? Klavye ve ekran okuyucu desteği ne kadar kritik? Bu yanıta göre kütüphane seçimi ve ek geliştirme maliyeti hesaba katılır.
Beşinci adım kütüphane seçimidir: Sabit yükseklik ve React odaklı proje için react-window yeterlidir. Dinamik içerik, framework esnekliği veya grid senaryosu varsa TanStack Virtual tercih edilir. Her iki kütüphane de aktif olarak bakımdadır; seçim büyük ölçüde bileşen tasarımı üzerindeki kontrol ihtiyacına bağlıdır. Monorepo yapılarında ortak bileşen kararları alınırken virtualizer seçimi tüm uygulamaları etkileyeceğinden bu adım özellikle önem kazanır.
Virtualization güçlü bir araçtır; ama her araç gibi belirli koşullarda değer üretir. Doğru koşul: öğe sayısı gerçekten çok fazla, öğeler karmaşık, kullanıcı tüm listeyi aynı anda görebilir ve ölçüm somut bir sorun ortaya koyuyor. Bu koşulların hepsi bir arada değilse, sayfalandırma veya sade bir DOM listesi genellikle daha az kırılgan ve bakımı çok daha kolay bir çözüm sunar.
Bir listenin virtualization gerektirip gerektirmediğini, tahminden önce DevTools kaydıyla öğrenmek mümkündür. Bu kayıt sizi hem gereksiz karmaşıklıktan hem de gecikmiş bir performans krizinden korur. Karar, öğe sayısına değil, ölçülen davranışa dayandığında sağlam bir temel üzerinde durur.