React Query ile Gereksiz Network İsteğini Azaltmak
React Query varsayılan olarak staleTime: 0 ile çalışır; bu, aynı query key'i kullanan farklı bileşenlerin her mount olduğunda ayrı ağ isteği başlatmasına yol açar. staleTime ve gcTime parametrelerini doğru yapılandırmak bu gereksiz trafiği önler ve uygulama büyüdükçe farkı artırır.
React Query'nin önbellek mekanizması ilk bakışta sezgisel görünür: bir kez çekilen veri bellekte tutulur, tekrar istek yapılmaz. Ancak varsayılan yapılandırmayla bileşen her mount olduğunda arka planda bir refetch tetiklenir; aynı veriyi farklı bileşenler kullanıyorsa bu sayı hızla çoğalır. Bunu fark etmeden kodu yazdığınızda, geliştirici araçlarının ağ sekmesi gereksiz isteklerle dolmuş olur.
Sorunun köküne inmeden önce şunu netleştirmek gerekir: React Query, aynı query key'e sahip istekleri otomatik olarak deduplicate eder; yani aynı anda birden fazla bileşen aynı veriyi isterse tek bir istek gider. Ne var ki "aynı anda" ifadesi kritiktir. Bileşenler farklı render döngülerinde ya da farklı zamanlarda mount olursa bu deduplication devreye girmez ve her birisi kendi refetch'ini başlatır.
staleTime ve gcTime (React Query v4 ve öncesinde cacheTime olarak bilinen parametre) bu davranışı biçimlendiren iki yapılandırma seçeneğidir. Bunları doğru ayarlamak, uygulama büyüdükçe ağ trafiğini önemli ölçüde düşürür ve kullanıcı deneyimini etkileyen gecikmeleri azaltır.
Varsayılan staleTime Neden Sorun Yaratır?
React Query'de staleTime varsayılan olarak 0'dır. Bu, önbellekteki verinin anında "bayat" (stale) sayıldığı anlamına gelir. Bir bileşen useQuery ile veri çektiğinde önbellekteki eski sonucu gösterir; ama aynı anda arka planda yeni bir istek başlatır ve gelen yanıtla ekranı günceller.
Tek bileşen için bu davranış kabul edilebilir görünür: kullanıcı anlık veriyi görür, en güncel hali sessizce yüklenir. Sorun, aynı query key'i kullanan ikinci bir bileşen farklı bir zamanda mount olduğunda başlar. staleTime: 0 olduğu için React Query bu bileşen için de veriyi bayat sayar ve yeni bir istek başlatır. Sayfada ürün listesi, sepet özeti ve öneri bloğu gibi birbirinden bağımsız üç bileşen aynı /api/products endpoint'ini kullanıyorsa, üçü de ayrı ayrı istek atar.
Bunu gözlemlemek için tarayıcının ağ sekmesi ya da React Query DevTools'un "Queries" paneli yeterlidir. Her istek ayrı bir satır olarak görünür ve payload boyutu aynıdır. Kaç bileşen aynı endpoint'i çekiyorsa o kadar istek görürsünüz.
staleTime Nasıl Çalışır, Hangi Değer Ne Anlama Gelir?
staleTime, önbellekteki verinin "taze" sayılacağı milisaniye cinsinden süredir. Bu süre dolmadan aynı query key'e yapılan yeni bir useQuery çağrısı arka planda istek başlatmaz; önbellekteki veriyi doğrudan kullanır.
useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 60_000, // 1 dakika
})
Bu yapılandırmayla ilk istek yapıldıktan sonraki 60 saniye boyunca aynı key'e yapılan her yeni useQuery çağrısı ağa çıkmaz. Bir bileşen unmount olup yeniden mount olsa bile, 60 saniyelik pencere dolmamışsa önbellekteki veriyi kullanır.
Değer Infinity olarak ayarlandığında veri hiçbir zaman bayat sayılmaz; yalnızca manuel invalidation veya uygulamanın kapatılması önbelleği sıfırlar. Statik içerik, nadiren değişen konfigürasyon verileri ya da oturum boyunca sabit kalan kullanıcı profili için bu mantıklıdır. Sık değişen veriler için Infinity kullanmak tersine işler: güncellenen veri kullanıcıya yansımaz ve yanlış veri görüntülenmeye devam eder.
staleTime için evrensel bir doğru değer yoktur. Verinin güncellenme sıklığı, kritikliği ve kullanıcı aksiyonlarıyla ilişkisi belirleyicidir.
gcTime ile staleTime Arasındaki Fark
İki parametre sık karıştırılır. staleTime, verinin ne kadar süre "taze" sayıldığını kontrol eder. gcTime ise kullanılmayan (inactive) bir sorgunun önbellekte ne kadar süre tutulacağını belirler; bu süre sonunda önbellekten silinir.
Bir sorgu "inactive" olur: onu kullanan tüm bileşenler unmount olduğunda. Kullanıcı bir sayfadan çıktığında o sayfaya özgü sorgular inactive duruma geçer. gcTime varsayılan olarak 5 dakikadır; bu süre içinde kullanıcı geri dönerse önbellek hâlâ yerinde olabilir ve önceki sonuçlar gösterilir.
useQuery({
queryKey: ['user-profile'],
queryFn: fetchUserProfile,
staleTime: 5 * 60_000, // 5 dakika taze
gcTime: 10 * 60_000, // 10 dakika önbellekte tut
})
gcTime'ı staleTime'dan kısa ayarlamak anlamsızdır. Veri henüz taze sayılırken önbellekten silinirse, bir sonraki useQuery çağrısı önbellekte hiçbir şey bulamaz ve yeniden istek başlatır. Pratikte gcTime her zaman staleTime'a eşit ya da daha uzun tutulmalıdır.
Aynı Query Key'i Paylaşan Bileşenler Arasında Deduplication
React Query'nin en güçlü yanlarından biri, aynı query key'i kullanan birden fazla useQuery çağrısını tek bir ağ isteğine indirgemesidir. Ancak bu deduplication'ın tam anlamıyla çalışması için bileşenlerin aynı render döngüsünde ya da istek henüz aktifken mount olması gerekir.
Senaryo şöyle işler: A bileşeni mount olur, istek başlar. Aynı anda ya da istek tamamlanmadan önce B bileşeni de mount olursa React Query ikinci bir istek başlatmaz; B bileşeni A'nın isteğiyle gelen sonucu paylaşır. Ancak istek tamamlanıp staleTime: 0 durumdayken B bileşeni daha sonra mount olursa, veri bayat sayıldığından yeni bir istek başlar.
Çok bileşenli mimarilerde veri akışını merkezi tutmak ve query key tasarımını tutarlı yapmak bu nedenle önem kazanır. Query key'lerin tam olarak eşleşmesi gerekir: ['products'] ile ['products', undefined] React Query gözünde farklı key'lerdir ve deduplication bu iki key arasında gerçekleşmez.
staleTime Yüksek Tutmanın Riskleri
staleTime'ı yüksek tutmanın da riski vardır. Gerçek zamanlı ya da sık güncellenen veriler için yanlış değer seçmek, kullanıcıya bayat veri gösterir.
Stok durumu, anlık fiyat bilgisi ya da canlı bildirim listesi için staleTime: 5 * 60_000 kullanmak, kullanıcının 5 dakika boyunca güncel olmayan veriyle etkileşmesine neden olabilir. Bu tür veriler için ya staleTime: 0 kalmalı ya da refetchInterval ile periyodik güncelleme eklenmelidir. Gereksiz veri çekiminin farklı kaynaklara bağlı olabileceğini göz önünde bulundurarak, staleTime artışını yalnızca değişim sıklığı düşük veriler için değerlendirin.
Ayrıca mikro frontend mimarisi ya da bağımsız bir React uygulaması başka bir uygulamayla aynı endpoint'i farklı bir QueryClient örneğiyle sorguluyorsa, deduplication ve önbellek paylaşımı devreye girmez. Her QueryClient kendi önbelleğini taşır; bu durumda staleTime ne kadar yüksek olursa olsun çift istek kaçınılmazdır.
Global Yapılandırma ve Sorgu Bazlı İnce Ayar
staleTime ve gcTime'ı her useQuery çağrısında ayrı ayrı belirtmek yerine QueryClient'ın global default'larından ayarlamak daha yönetilebilir bir yapı oluşturur.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
gcTime: 5 * 60_000,
},
},
})
Bu yapılandırma tüm sorgular için geçerli olur. Belirli bir sorgunun farklı davranması gerekiyorsa, o sorgunun useQuery çağrısında değer belirtmek global default'ı ezer.
useQuery({
queryKey: ['live-prices'],
queryFn: fetchLivePrices,
staleTime: 0,
refetchInterval: 10_000,
})
Global default belirlemeden önce uygulamanızdaki sorguları gruplara ayırmak mantıklıdır: "oturum boyunca değişmez" grubuna staleTime: Infinity; "dakikalar içinde değişebilir" grubuna 1-5 dakika; "anlık değişebilir" grubuna 0 veya kısa bir aralık verilebilir. Veri paylaşım mekanizmalarının render maliyetini nasıl etkilediğini göz önünde tutarsanız, staleTime: 0 ile çalışan sorgularda çok sayıda refetch hem ağ hem de re-render yüküne dönüşür.
Refetch Tetikleyicilerini Kontrol Altına Almak
staleTime düzenlense bile React Query'nin diğer refetch tetikleyicileri aktif kalmayı sürdürür: refetchOnWindowFocus, refetchOnMount, refetchOnReconnect. Bunlar varsayılan olarak true'dur.
refetchOnWindowFocus: true ile kullanıcı başka bir sekmeye geçip geri döndüğünde, bayat sayılan tüm sorgular yeniden çekilir. staleTime: 60_000 ayarlamış olsanız bile, 60 saniye dolmuşsa bu tetikleyici yeni istek başlatır. Bu tetikleyicileri tamamen kapatmak uygulamanın eski veri göstermesine yol açabilir; seçici kapatmak daha güvenlidir.
useQuery({
queryKey: ['app-config'],
queryFn: fetchAppConfig,
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnMount: false,
})
Statik konfigürasyon verisi için bu üçlü kombinasyon, uygulama yaşam döngüsü boyunca tek istek yapılmasını sağlar. Yayına çıkmadan önce ağ isteklerini gözden geçirmek bu tür gereksiz trafiği yakalamayı kolaylaştırır. Gereksiz hesaplamaları önlemek nasıl profil gerektiriyorsa, gereksiz ağ isteklerini de ölçmeden azaltmaya çalışmak tutarlı bir yaklaşım değildir. Ağ sekmesinde belirli bir endpoint'e giden istek sayısını saymak, karar için yeterli veriyi sağlar.
Sorunu Teşhis Etmek İçin Pratik Kontrol Sırası
Gereksiz istek sorununu çözmede önce teşhis, sonra müdahale gelir.
İlk adım: aynı endpoint'e kaç farklı useQuery çağrısı yaptığınızı ve bunların query key'lerinin tam eşleşip eşleşmediğini kontrol edin. İkinci adım: bu sorguların staleTime değerlerini listeleyin; herhangi biri belirtilmemişse global default devreye girer, o da belirtilmemişse 0 kabul edilir. Üçüncü adım: her sorgu için gcTime'ın staleTime'dan uzun olduğunu doğrulayın. Son adım: refetchOnWindowFocus, refetchOnMount gibi tetikleyicilerin o sorgu için gereksiz olup olmadığını değerlendirin.
Render optimizasyonlarının etkisini doğrulamak için izlenen adımlar ile bu kontrol sırası benzerdir: ölçüm, yalıtma, müdahale, tekrar ölçüm. Müdahale olmadan değerlendirme yapmazsanız, değişikliğin işe yarıyıp yaramadığını bilemezsiniz.
staleTime ve gcTime küçük yapılandırma değerleri gibi görünür; ancak uygulamanın büyüdükçe bu değerlerin ihmal edilmesi, gereksiz ağ trafiği ve buna bağlı re-render zincirleri şeklinde geri döner. Sorunu fark etmek çoğunlukla DevTools'un ağ sekmesinde alışılmadık sayıda tekrarlayan istek görmekle başlar.
Yapılandırmayı değiştirmeden önce hangi sorgu grubunun ne sıklıkla güncellendiğini anlamak, doğru staleTime değerini belirlemenin tek güvenilir yoludur. Veriye ulaşmanın ne kadar kolay olduğu değil, verinin ne kadar süreyle geçerli kaldığı belirleyicidir. Bunu baz aldığınızda kütüphane zaten yapması gerekeni yapar.