Bölüm 10 / 16
Veri çekme
fetch, TanStack Query, önbellek ve yeniden getirme.
11 dakikalık okuma
3. derste effect'le fetch'in elle üçlüsünü kurdun —
yükleniyor/hata durumu, yarış, AbortController — ve dürüst notu
düşmüştük: "üretimde bu işin sahibi TanStack Query'dir." Söz burada ödeniyor. Ama
önce kavram: sunucu durumu, istemci durumundan farklı bir türdür — sahibi
sen değilsin, bayatlar, paylaşılır. TanStack Query bir "fetch kütüphanesi" değil,
bu türün durum yöneticisidir: queryKey, staleTime,
geçersiz kılma, mutation ve optimistic update. Kapanışta Angular köprüsü:
HttpClient + interceptor dünyasından gelen el, React'te bu araçların
karşılığını nerede bulur?
01Ne öğreneceksin
- Sunucu durumu ≠ istemci durumu: beş fark ve bunların araç seçimine etkisi
useQueryanatomisi:queryKey(bağımlılık beyanının kardeşi),queryFn,signal- Önbellek ömrü:
staleTimevsgcTime; "bayat ama göster, arkada tazele" zihni useMutation+invalidateQueries: yazma sonrası okumayı tazeleme deseni- Optimistic update kavramı: anında ekran, hatada geri alma
HttpClient/interceptor kıyası: fetch sarmalayıcı, taban URL, hata sözleşmesi (jeton işi 13. hatta)
02Sunucu durumu başka bir türdür
2. dersten beri kurduğun her state istemci durumuydu: sen yaratırsın, tek sahibi uygulamandır, sen değiştirmedikçe değişmez. Sunucudan çekilen başvuru listesi bu tanıma uymaz — o, başka bir makinedeki gerçeğin anlık fotoğrafıdır:
| İstemci durumu | Sunucu durumu | |
|---|---|---|
| Sahibi | Uygulaman | Sunucu — sen kopyayı tutarsın |
| Tazelik | Hep günceldir | Çekildiği an bayatlamaya başlar (başkası kayıt eklemiş olabilir) |
| Erişim | Senkron | Asenkron — gecikme ve hata her zaman masada |
| Paylaşım | Bileşen/ağaç ölçeğinde | Aynı veri birçok ekranda: tek istek, ortak önbellek istenir |
| Yaşam döngüsü | set → render | çek → önbellekle → göster → tazele → çöp topla |
9. dersin sınır çizgisi buradan gelir: bu yaşam döngüsünü useState ya
da store ile elle yönetmek, 3. derste yaşadığın üç eksiği (durum bayrakları, yarış,
önbellek yokluğu) her bileşende yeniden çözmektir. TanStack Query'nin doğru tanımı
bu yüzden "fetch kütüphanesi" değil asenkron durum yöneticisidir: HTTP'yi
sen yaparsın (fetch'le — 8. hattın 17. dersi aynen geçerli), o fotoğrafın yaşam
döngüsünü yönetir.
03useQuery: anahtar, fonksiyon, üç durum
3. dersin elle üçlüsünün devri — aynı iş, beyanla:
import { useQuery } from "@tanstack/react-query";
function BasvuruListesi({ filtre }: { filtre: string }) {
const { data, isPending, isError } = useQuery({
queryKey: ["basvurular", filtre], // önbellek adresi + bağımlılık beyanı
queryFn: async ({ signal }) => { // signal: Query'nin AbortController'ı
const y = await fetch(`/api/basvurular?durum=${filtre}`, { signal });
if (!y.ok) throw new Error(`HTTP ${y.status}`); // fetch 404'te throw ETMEZ (8. hat)
return y.json() as Promise<Basvuru[]>;
},
});
if (isPending) return <Yukleniyor />;
if (isError) return <HataKutusu />;
return <ul>{data.map(b => <BasvuruSatiri key={b.id} basvuru={b} />)}</ul>;
// dört durumlu ekran disiplini (5. ders): boş dizi ayrıca ele alınır
}
queryKey iki iş birden görür ve dersin kalbi budur. Birincisi
bağımlılık beyanı: filtre anahtarın içindedir; değişince Query yeni
anahtar için veri çeker — 3. derste effect bağımlılığına yazdığın şeyin kardeşidir
ve aynı kural geçerlidir: queryFn'in kullandığı her değişken
anahtarda olmalıdır, yoksa iki farklı filtre aynı önbellek gözüne yazar ve yanlış
veri gösterirsin. İkincisi önbellek adresi: aynı anahtarla iki ayrı bileşen
useQuery çağırırsa tek istek atılır, ikisi aynı önbelleği okur —
"tek istek, ortak önbellek" ihtiyacı kutudan çıkar. Yarışı da elle çözmezsin:
Query, anahtar değişince eski isteğin signal'ini iptal eder — 3. derste
elle yazdığın AbortController artık mekanizmanın içindedir (ve
switchMap kıyası tam kapanır: 10. hattın 5. dersindeki davranışın
React dünyasındaki sahibi budur). Kurulum tek seferliktir: kökte
QueryClientProvider — 8. dersin sağlayıcı bileşen kalıbının aynısı.
04Önbellek ömrü: bayat ama göster
Query'nin varsayılan felsefesi web'in en iyi pratiğidir: stale-while-revalidate — "elimde bayat kopya varsa önce onu göster, arkada tazele, gelince sessizce değiştir". Kullanıcı boş ekran yerine anında içerik görür. İki ayrı saat bu davranışı yönetir ve mülakatta en çok karışan ikilidir:
| Ayar | Sorusu | Varsayılan | Dolunca ne olur |
|---|---|---|---|
staleTime | "Bu veri ne kadar süre taze sayılır?" | 0 — anında bayat | Veri gösterilmeye devam eder ama bir sonraki fırsatta (bileşen mount, pencere odağı, ağın dönüşü) arkada yeniden çekilir |
gcTime | "Kullanılmayan önbellek ne kadar saklanır?" | 5 dakika | Hiçbir bileşen o anahtarı kullanmıyorsa göz çöpe gider; sonraki kullanım sıfırdan yükler |
Ayar kararı veri türüne göre verilir: saniyede değişen borsa verisi
staleTime: 0 ile yaşar; günde bir değişen ünvan listesi
staleTime: 24 saat alır ve gereksiz trafik biter.
refetchOnWindowFocus varsayılanı da bu felsefenin parçasıdır: kullanıcı
sekmeye dönünce bayat sorgular tazelenir — "başka sekmede kayıt ekledim, dönünce
listede yok" şikâyetini kutudan çözer; şaşırtıcı bulursan kapatabilirsin ama önce ne
kazandırdığını bil. Durum bayrakları da incelmiştir: ilk yüklemede
isPending, arkadaki tazelemede isFetching — kullanıcıya
dev spinner değil köşede küçük bir "yenileniyor" imi gösterirsin (5. dersin "boş ≠
yükleniyor" disiplininin devamı: bayat ≠ yok).
05useMutation: yazmak ve okumayı tazelemek
Sorgu okur; yazma (POST/PUT/DELETE) mutation'dır — bir olayın sonucudur
(3. dersin turnusolu: "hangi olayın sonucu?" — gönderim olayının), bu yüzden
kendiliğinden değil mutate çağrısıyla çalışır:
import { useMutation, useQueryClient } from "@tanstack/react-query";
function YeniBasvuruFormu() {
const qc = useQueryClient();
const ekle = useMutation({
mutationFn: (yeni: YeniBasvuru) =>
apiIstek<Basvuru>("/basvurular", { method: "POST", body: JSON.stringify(yeni) }),
onSuccess: () => {
// yazdık → elimizdeki TÜM başvuru kopyaları artık şüpheli: bayat damgala
qc.invalidateQueries({ queryKey: ["basvurular"] });
},
});
// ekle.mutate(taslak) — 4. dersin çift gönderim kilidi de hazır: ekle.isPending
}
invalidateQueries bu desenin kilididir: sunucuya yazdığın an,
önbellekteki ilgili tüm gözler (anahtar öneki eşleşir:
["basvurular"], ["basvurular", "beklemede"]'yi de kapsar)
bayat damgalanır ve ekranda kullanılanlar hemen tazelenir. "Listeye elle push
yapayım" refleksinden daha kaba ama çok daha güvenli bir varsayılandır — sunucu ne
ürettiyse (id, damga, hesaplanan alanlar) onu gösterirsin. İnce ayar isteyen yerde
optimistic update devreye girer: onMutate'te önbelleğe
beklenen sonucu hemen yazarsın (ekran anında güncellenir), sunucudan hata
dönerse onError'da sakladığın önceki fotoğrafa geri alırsın,
onSettled'da yine de geçersiz kılarsın — gerçek her durumda sunucudan
gelir. Beğeni düğmesi gibi "büyük ihtimal başarılı, gecikme hissedilmesin" işlerin
desenidir; kavramı bilmek yeter, ilk projede varsayılan yol geçersiz kılmadır.
06Angular köprüsü: HttpClient ve interceptor nerede?
10. hattın 6. dersinden gelen elin arayacağı üç şeyin React dünyasındaki karşılığı:
| Angular'da | React dünyasında |
|---|---|
HttpClient — tipli, gövdesi hazır istemci | Yerleşik yok: çıplak fetch + kendi sarmalayıcın; ya da axios/ky gibi bir istemci kütüphanesi |
| Interceptor — her isteğe başlık, ortak hata işleme | Sarmalayıcı fonksiyonun içi; axios seçersen onun interceptor'ları |
| Önbellek/tekrar/yarış — RxJS operatörleriyle elle | TanStack Query'nin işi — istemciden bağımsız katman |
Katmanları ayır: Query ne zaman ve neyin çekileceğini yönetir, HTTP'nin
nasılını bilmez — queryFn'e ne verirsen onu çalıştırır. HTTP
sözleşmesi senin sarmalayıcında toplanır (4. hattın DRY'ı: tek yerde):
const API_KOK = "/api";
async function apiIstek<T>(yol: string, sec: RequestInit = {}): Promise<T> {
const yanit = await fetch(`${API_KOK}${yol}`, {
...sec,
headers: { "Content-Type": "application/json", ...sec.headers },
});
if (!yanit.ok) {
// hata gövdesini de taşı: 6. hattın hata sözleşmesi (ProblemDetails) burada okunur
throw new ApiHatasi(yanit.status, await yanit.text());
}
return yanit.json() as Promise<T>;
}
Kimlik jetonunu başlığa eklemek de bu sarmalayıcının işi olacak — ama jetonun
nerede saklanacağı ve nasıl tazeleneceği güvenlik konusudur ve 13. hatta işlenecek;
buradaki görev, tek kapılı bir istek katmanı bırakmaktır ki o gün tek dosya
değişsin. Son not — tip dürüstlüğü: as Promise<T> bir iddiadır,
doğrulama değil (9. hattın uyarısı); sunucuya güvenmediğin sınırda şemayı gerçekten
doğrulamanın aracı (zod) 12. derste form doğrulamayla birlikte gelecek.
07Mini alıştırma — Elle üçlüyü emekliye ayır
Yaklaşık 20 dakika · StackBlitz projene @tanstack/react-query ekle; API yerine gecikmeli sahte promise kullan
- Kökte
QueryClient+QueryClientProviderkur. 3. dersteki elle fetch effect'iniuseQuery'ye taşı (queryKey: ["basvurular", filtre]); yükleniyor/hata/veri ekranlarının 5. dersteki ayırt edici birleşim disipliniyle kurulduğunu koru. - Ortak önbelleği kanıtla: aynı anahtarla iki ayrı bileşende
useQueryçağır, sahte API'yeconsole.countkoy — isteğin bir kez atıldığını gör. - Anahtar disiplinini boz:
filtre'yiqueryFn'de kullanıp anahtardan çıkar; iki filtrenin aynı önbellek gözüne yazdığını ve yanlış listenin göründüğünü sahnele, sonra düzelt — "queryFn'in kullandığı her şey anahtarda" kuralını not et. staleTimedeneyi: önce varsayılanla sekme odağını değiştirip dönünce arkada tazeleme olduğunu (isFetching imiyle) gör; sonrastaleTime: 60_000verip aynı hareketin istek atmadığını doğrula.- Yazma zinciri: sahte POST'lu bir
useMutationkur;onSuccess'teinvalidateQueries(["basvurular"])ile listenin kendiliğinden tazelendiğini gör;mutatebeklerken düğmeyiisPendingile kilitle (4. dersin çift gönderim senedi). - Kavram turu: optimistic update akışını (onMutate → hata → geri alma → onSettled) kod yazmadan, beş adımlık bir sıralama olarak defterine çiz; hangi ekranlarında meşru olacağını bir cümleyle gerekçele.
08Kendini sına
Sunucu durumu ile istemci durumu arasındaki farkları say ve bu ayrımın araç seçimini nasıl belirlediğini anlat.
İstemci durumunu uygulama yaratır ve tek sahibi kendisidir: senkron erişilir, sen değiştirmedikçe değişmez — useState/useReducer, gerekirse context ya da store ile yönetilir. Sunucu durumu ise başka makinedeki gerçeğin anlık fotoğrafıdır: sahibi sunucudur, çekildiği an bayatlamaya başlar (başka kullanıcı ya da sekme değiştirmiş olabilir), erişimi asenkrondur (gecikme ve hata hep masada), aynı veri birçok ekranda gerektiği için tek istek + ortak önbellek ister ve "çek → önbellekle → tazele → çöp topla" diye bir yaşam döngüsü vardır. Bu döngüyü useState ya da store'la elle yönetmek, durum bayraklarını, yarışı ve önbelleği her bileşende yeniden icat etmektir. Araç seçimi bu yüzden türe göre yapılır: istemci durumu merdiveni (9. ders) ayrı, sunucu durumu için TanStack Query (Redux'lu projede RTK Query) ayrı. Query'nin doğru tanımı da buradan gelir: fetch kütüphanesi değil, asenkron durum yöneticisi — HTTP'yi sen yaparsın, fotoğrafın yaşam döngüsünü o yönetir.
queryKey'in iki görevini anlat. "queryFn'in kullandığı her değer anahtarda olmalı" kuralı çiğnenirse ne olur ve bu kural sana neyi hatırlatıyor?
Birinci görev bağımlılık beyanı: anahtar değişince Query
yeni anahtar için veri çeker; eski isteğin signal'i iptal edilir —
3. derste elle yazılan AbortController + yarış çözümü mekanizmaya gömülüdür ve
Angular'daki switchMap davranışının React dünyasındaki karşılığı budur. İkinci
görev önbellek adresi: sonuçlar anahtarla saklanır; aynı anahtarı kullanan tüm
bileşenler tek isteği paylaşır ve aynı önbelleği okur. Kural çiğnenirse —
filtre queryFn'de kullanılıp anahtara yazılmazsa — iki
farklı filtrenin sonucu aynı önbellek gözüne yazılır: kullanıcı filtreyi
değiştirir, Query "anahtar aynı" diye eski (yanlış) veriyi gösterir, üstelik
tazeleme de yanlış gözü günceller. Bu, 3. dersteki exhaustive-deps kuralının
kardeşidir: orada effect'in kullandığı her reaktif değer bağımlılıkta, burada
queryFn'in kullandığı her değer anahtarda beyan edilir — iki mekanizma da
"kullandığını beyan et" sözleşmesiyle doğru çalışır.
staleTime ile gcTime farkını, stale-while-revalidate felsefesini ve isPending/isFetching ayrımını anlat.
İki ayar iki farklı soruya cevaptır. staleTime:
"veri ne kadar süre taze sayılır?" — dolunca veri kaybolmaz, gösterilmeye devam
eder ama ilk fırsatta (yeni mount, pencere odağı, ağın geri gelmesi) arka planda
yeniden çekilir; varsayılan 0'dır, yani her şey anında bayattır.
gcTime: "hiçbir bileşenin kullanmadığı önbellek gözü ne kadar
saklanır?" — dolunca göz çöpe gider ve sonraki kullanım sıfırdan yükler;
varsayılan 5 dakikadır. Felsefe stale-while-revalidate'tir: bayat kopyayı hemen
göster, arkada tazele, gelince sessizce değiştir — kullanıcı boş ekran yerine
anında içerik görür. Bu yüzden iki ayrı bayrak vardır: isPending
yalnız ilk yüklemede (önbellekte hiç veri yokken) doğrudur ve büyük yükleme
ekranını hak eder; isFetching arka plan tazelemesinde de doğrudur ve
köşede küçük bir "yenileniyor" imiyle gösterilir. Ayar kararı veri türüne göre
verilir: sık değişen veri düşük, nadiren değişen sözlük verisi yüksek
staleTime alır.
Yazma sonrası ekranın güncellenmesi için iki deseni (geçersiz kılma, optimistic update) karşılaştır; React tarafında HttpClient/interceptor boşluğunu nasıl doldurursun?
Varsayılan desen geçersiz kılmadır: mutation başarınca
invalidateQueries ile ilgili anahtar öneki bayat damgalanır, ekranda
kullanılan sorgular kendiliğinden tazelenir. Kaba ama güvenlidir — id, damga,
hesaplanan alanlar dahil gerçek sunucudan gelir; ilk tercih budur. Optimistic
update gecikme hissini silmek içindir: onMutate'te önbelleğe
beklenen sonuç hemen yazılır (ekran anında döner), önceki fotoğraf saklanır;
sunucu hata dönerse onError fotoğrafı geri koyar;
onSettled her durumda geçersiz kılar ki gerçek yine sunucudan
gelsin. Beğeni gibi yüksek başarı olasılıklı, anında geri bildirim isteyen işlerin
desenidir ve geri alma kodu yazılmadan kullanılmaz. HttpClient boşluğu katman
ayrımıyla dolar: Query neyin ne zaman çekileceğini yönetir, HTTP'nin nasılı tek
kapılı bir sarmalayıcıda toplanır — taban URL, JSON başlıkları, y.ok
kontrolü ve hata gövdesini taşıyan özel hata tipi orada yaşar; interceptor
ihtiyacı bu fonksiyonun içidir (axios seçilirse onun interceptor'ları). Kimlik
jetonu da oraya eklenecek — saklama ve tazeleme kararları 13. hattın
konusu.
09Sınav köşesi
Yazılıda nasıl sorulur
(1) Sunucu durumu ile istemci durumu arasındaki farklar nelerdir? (2) TanStack
Query neden "fetch kütüphanesi" değildir? (3) queryKey'in iki görevi
nedir? (4) Anahtara yazılmayan bağımlılık hangi hataya yol açar?
(5) staleTime ile gcTime farkı nedir?
(6) Stale-while-revalidate ne demektir? (7) isPending ile
isFetching ne zaman ayrışır? (8) invalidateQueries ne
yapar; anahtar öneki nasıl eşleşir? (9) Optimistic update akışının adımları
nelerdir? (10) React'te interceptor ihtiyacı nasıl karşılanır?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Ayrım — "Sunucu verisi ayrı bir durum türüdür: sahibi sunucu, çekildiği an bayat, erişimi asenkron, önbelleği ortak — bu yüzden useState ya da store değil, asenkron durum yöneticisi ister." (2) Mekanizma — "useQuery'de queryKey hem önbellek adresi hem bağımlılık beyanıdır — queryFn'in kullandığı her değer anahtara girer; yarış ve iptal mekanizmanın içindedir, 3. derste elle yazdığım AbortController'ın devri." (3) Tazelik — "Varsayılan felsefe stale-while-revalidate: bayatı göster, arkada tazele; staleTime tazelik, gcTime saklama süresidir." (4) Yazma — "Mutation sonrası varsayılanım invalidateQueries'tir — gerçek sunucudan gelir; gecikme hissi kritikse optimistic update'i geri almasıyla birlikte kurarım." (5) Kıdem vuruşu — "Katmanları ayırırım: HTTP'nin nasılı tek kapılı sarmalayıcıda (taban URL, hata sözleşmesi, yarın jeton), neyin ne zaman çekileceği Query'de."
