React projelerinde sonsuz kaydırma (infinite scroll), kullanıcı sayfayı aşağı kaydırdıkça otomatik olarak yeni içerik yükleyen popüler bir UX desenidir. Bu yazıda, Intersection Observer API'sini kullanarak React'te verimli ve temiz bir sonsuz kaydırma bileşeni nasıl oluşturulacağını adım adım anlatıyoruz. Scroll olaylarını dinlemek yerine Intersection Observer kullanmanın sağladığı performans avantajlarına odaklanacağız.
Neden Intersection Observer Kullanmalıyız?
Geleneksel scroll olayı dinleme yöntemi, her kaydırmada yeniden boyutlandırma hesaplamaları yaparak tarayıcıyı yorar ve gereksiz yeniden oluşturmalara neden olur. Intersection Observer ise tarayıcının kendi iş parçacığında çalışır ve yalnızca hedef öğe görünüm alanına girdiğinde veya çıktığında geri çağrı yapar. Bu sayede performans önemli ölçüde iyileşir. Ayrıca Servis Çalışanları (Service Workers) ile Offline Web Uygulamaları yazımızda olduğu gibi, tarayıcının sunduğu modern API'leri kullanmak uygulamanızı geleceğe taşır.
React Bileşen Tasarımı
İlk adım, sonsuz kaydırmayı yönetecek bir useInfiniteScroll kancası (hook) oluşturmaktır. Bu kanca, bir referans (ref) alarak, o referansın görünürlüğünü izler ve görünür olduğunda yeni veri yüklemek için bir geri çağrı tetikler.
Adım 1: useRef ve Intersection Observer'ı Ayarlama
Aşağıdaki örnekte, useRef ile bir hedef öğe oluşturuyor, ardından useEffect içinde Intersection Observer'ı başlatıyoruz. Observer, hedef öğe görünür hale geldiğinde onIntersect fonksiyonunu çağırır.
import { useRef, useEffect, useCallback } from 'react';
function useInfiniteScroll(callback, options) {
const targetRef = useRef(null);
const handleIntersect = useCallback((entries) => {
if (entries[0].isIntersecting) {
callback();
}
}, [callback]);
useEffect(() => {
const observer = new IntersectionObserver(handleIntersect, options);
if (targetRef.current) {
observer.observe(targetRef.current);
}
return () => observer.disconnect();
}, [handleIntersect, options]);
return targetRef;
}
Adım 2: Ana Bileşende Kullanma
useInfiniteScroll kancasını ana bileşeninizde kullanarak, liste sonuna bir gözetleyici (sentinel) öğesi ekleyin. Bu öğe, sayfa sonuna ulaşıldığında tetiklenir.
function ProductList() {
const [products, setProducts] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
const newProducts = await fetchProducts(page);
setProducts(prev => [...prev, ...newProducts]);
setPage(prev => prev + 1);
setHasMore(newProducts.length > 0);
setLoading(false);
}, [page, loading, hasMore]);
const sentinelRef = useInfiniteScroll(loadMore, { threshold: 0.1 });
return (
<div>
{products.map(product => <ProductCard key={product.id} product={product} />)}
<div ref={sentinelRef} style={{ height: 1 }} />
{loading && <Spinner />}
</div>
);
}
Performans İyileştirmeleri ve Dikkat Edilmesi Gerekenler
- Bileşen yeniden oluşturmalarını azaltın:
callbackfonksiyonunuuseCallbackile sarmalayın, böylece observer her yeniden oluşturmada yeniden başlatılmaz. - Yükleme durumunu yönetin: Aynı anda birden fazla istek göndermemek için
loadingbayrağı kullanın. - Dizi anahtarları (keys) benzersiz olsun: Liste öğelerinde
keyprop'u olarakidkullanın. - Observer'ı temizleyin:
useEffecttemizleme fonksiyonundaobserver.disconnect()çağırmayı unutmayın. - Root margin ile erken yükleme:
rootMargin: '200px'gibi bir değerle, kullanıcı görmeden önce yüklemeye başlayabilirsiniz.
Scroll Olayına Karşı Intersection Observer: Karşılaştırma
| Özellik | Scroll Olayı | Intersection Observer |
|---|---|---|
| Performans | Düşük (ana iş parçacığında sürekli hesaplama) | Yüksek (tarayıcı iş parçacığında, optimize) |
| Kullanım Kolaylığı | Manuel hesaplama gerekir | Kolay, deklaratif API |
| Esneklik | Her türlü scroll pozisyonu hesaplanabilir | Yalnızca görünürlük değişiklikleri |
| Tarayıcı Desteği | Evrensel | Modern tarayıcılar (polyfill gerekebilir) |
Sık Yapılan Hatalar
- Observer'ı her render'da yeniden oluşturmak:
useEffectbağımlılıklarını doğru ayarlamazsanız observer sürekli yenilenir ve performans düşer. - Hedef öğeyi koşullu render etmek: Sentinel öğesi her zaman DOM'da bulunmalı, aksi halde observer çalışmaz. Yükleme sırasında bile görünür kalmasını sağlayın.
- Yüklenen verileri sınırsız biriktirmek: Sonsuz kaydırma büyük listelerde bellek sorununa yol açabilir. Sanal kaydırma (virtual scroll) veya sayfalama düşünün. Cursor-Based Pagination REST API yazımızda backend tarafında imleç tabanlı sayfalama ile bu sorunu nasıl çözebileceğinizi anlatıyoruz.
Gerçek Zamanlı Veri ile Kullanım
Sonsuz kaydırma, canlı bildirimler veya gerçek zamanlı güncellemelerle birleştirildiğinde daha karmaşık hale gelir. WebSocket vs Server-Sent Events (SSE) yazımızda hangi teknolojinin hangi senaryoda daha uygun olduğunu karşılaştırıyoruz. Sonsuz kaydırma ile gerçek zamanlı veri akışını birleştirirken, yeni verilerin listeye eklenme sırasına ve tekrarları önlemeye dikkat edin.
Sonsuz kaydırma, doğru uygulandığında kullanıcı deneyimini büyük ölçüde iyileştiren bir desendir. Intersection Observer API'si ile bu işlemi performanslı ve temiz bir şekilde yapabilirsiniz. Uygulamanızda deneyerek sonuçları gözlemleyin ve ihtiyaçlarınıza göre özelleştirin.
Sık Sorulan Sorular
Intersection Observer neden scroll olayından daha performanslıdır?
Scroll olayı ana iş parçacığında her kaydırmada tetiklenir ve yeniden boyutlandırma hesaplamaları yapar. Intersection Observer ise tarayıcının ayrı bir iş parçacığında çalışır ve yalnızca hedef öğenin görünürlüğü değiştiğinde geri çağrı yapar. Bu, gereksiz işlemleri ortadan kaldırır ve daha akıcı bir kullanıcı deneyimi sağlar.
Sonsuz kaydırmada sayfa numarası yerine cursor kullanmalı mıyım?
Evet, özellikle büyük ve dinamik veri kümelerinde cursor tabanlı sayfalama daha güvenilirdir. Sayfa numarası kullanıldığında, veriler eklenir veya silinirse kayma (offset) sorunları oluşabilir. Cursor tabanlı yöntem, her isteğin bir sonraki veri parçasını doğru şekilde almasını sağlar.
Sonsuz kaydırma ile birlikte arama filtresi nasıl uygulanır?
Filtre değiştiğinde mevcut listeyi sıfırlayıp yeniden başlatmanız gerekir. state'i sıfırlayın (sayfa/cursor'ı ilk değere döndürün) ve observer'ı yeniden tetikleyin. Filtre ve sonsuz kaydırma state'lerini ayrı yönetmek, beklenmedik davranışları önler.
Intersection Observer polyfill gerekir mi?
Eski tarayıcıları desteklemeniz gerekiyorsa (örneğin Internet Explorer 11), Intersection Observer polyfill ekleyebilirsiniz. Çoğu modern tarayıcı (Chrome, Firefox, Safari, Edge) yerel olarak destekler. Polyfill için 'intersection-observer' npm paketini kullanabilirsiniz.






