Skip to content

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
  • useQuery anatomisi: queryKey (bağımlılık beyanının kardeşi), queryFn, signal
  • Önbellek ömrü: staleTime vs gcTime; "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 durumuSunucu durumu
SahibiUygulamanSunucu — sen kopyayı tutarsın
TazelikHep günceldirÇekildiği an bayatlamaya başlar (başkası kayıt eklemiş olabilir)
ErişimSenkronAsenkron — gecikme ve hata her zaman masada
PaylaşımBileşen/ağaç ölçeğindeAynı 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:

AyarSorusuVarsayılanDolunca ne olur
staleTime"Bu veri ne kadar süre taze sayılır?"0 — anında bayatVeri 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 dakikaHiç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'daReact dünyasında
HttpClient — tipli, gövdesi hazır istemciYerleş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şlemeSarmalayıcı fonksiyonun içi; axios seçersen onun interceptor'ları
Önbellek/tekrar/yarış — RxJS operatörleriyle elleTanStack 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

  1. Kökte QueryClient + QueryClientProvider kur. 3. dersteki elle fetch effect'ini useQuery'ye taşı (queryKey: ["basvurular", filtre]); yükleniyor/hata/veri ekranlarının 5. dersteki ayırt edici birleşim disipliniyle kurulduğunu koru.
  2. Ortak önbelleği kanıtla: aynı anahtarla iki ayrı bileşende useQuery çağır, sahte API'ye console.count koy — isteğin bir kez atıldığını gör.
  3. Anahtar disiplinini boz: filtre'yi queryFn'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.
  4. staleTime deneyi: önce varsayılanla sekme odağını değiştirip dönünce arkada tazeleme olduğunu (isFetching imiyle) gör; sonra staleTime: 60_000 verip aynı hareketin istek atmadığını doğrula.
  5. Yazma zinciri: sahte POST'lu bir useMutation kur; onSuccess'te invalidateQueries(["basvurular"]) ile listenin kendiliğinden tazelendiğini gör; mutate beklerken düğmeyi isPending ile kilitle (4. dersin çift gönderim senedi).
  6. 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."