Skip to content

Bölüm 3 / 16

useEffect

Yan etkiler, bağımlılık dizisi, temizleme.

11 dakikalık okuma

Render safttır: tarif üretir, dünyaya dokunmaz. Ama gerçek uygulama dünyaya dokunur — sunucudan veri çeker, zamanlayıcı kurar, olaya abone olur. React'te bu işlerin kapısı useEffect'tir ve React'in en çok yanlış kullanılan aracıdır: stale closure, sonsuz döngü, unutulan temizlik, yarış durumu — mülakatların dört klasiği de bu derste. İlke, Angular'daki effect için öğrendiğinin aynısı (10. hattın 9. dersi): effect son çaredir — ama önce doğru kullanmayı öğrenmelisin ki neyin son çare olduğunu bilesin.

01Ne öğreneceksin

  • Yan etki nedir; useEffect'in zamanlaması — "render sonrası senkronizasyon"
  • Bağımlılık dizisinin üç biçimi; "bağımlılık listesi seçim değil, beyandır"
  • İki klasik tuzak: stale closure ve sonsuz döngü — teşhis ve çözüm kalıpları
  • Temizlik fonksiyonu (ngOnDestroy simetrisi) ve StrictMode çift çalıştırmasının anlamı
  • Effect'te veri çekme: yarış durumu ve AbortController (8. hattın 17. dersi geri döner)
  • Effect son çare: effect'e hiç girmemesi gereken üç iş sınıfı

02Yan etki ve zamanlama: render sonrası senkron

1. dersin sözleşmesi: render fonksiyonu saf kalır — içinde fetch, setInterval, document.title ataması, abonelik yoktur (StrictMode'un çift çağrısı tam bu ihlalleri yakalamak içindir). Bu işlerin adı yan etkidir ve yeri useEffect'tir:

useEffect(() => {
  document.title = `${gorunen.length} başvuru`;   // React dışı dünyaya dokunuş
}, [gorunen.length]);   // "bu değerler değişmedikçe tekrar çalışma"

Zamanlama kritik: effect, render ve DOM'a yazma (commit) bittikten sonra çalışır — ekran güncellenmiştir, effect araya girip çizimi bloklamaz. Angular simetrisi zihni hızlandırır ama bir farkla: ngOnInit/ngOnDestroy "kuruldu/yıkıldı" anlarını işaretler; useEffect ise "React dışı bir sistemi state'le senkron tut" der. Doğru okuma "mount olunca çalıştır" değil, "şu değerlerle dış dünyayı eşitle"dir — bu cümle, dersin sonunda neyin effect olmadığını da söyleyecek.

03Bağımlılık dizisi: seçim değil, beyan

BiçimNe zaman çalışırTipik iş
useEffect(fn)Her render sonrasıNeredeyse hiçbir zaman istediğin şey değil
useEffect(fn, [])Yalnız ilk render sonrası (+ temizlik sökülürken)Bir kez kurulan abonelik, ilk veri yükü
useEffect(fn, [a, b])İlk render + a ya da b değişince (Object.is)Parametreye bağlı senkron: id değişti → yeniden çek

En önemli kural: bağımlılık listesi bir ayar düğmesi değil, gerçeğin beyanıdır. Effect'in içinde okuduğun her reaktif değer (props, state, onlardan türeyenler) listede olmak zorundadır — "daha az çalışsın" diye listeden değer gizlemek, effect'i eski fotoğrafla (2. dersin snapshot'ı) çalışmaya mahkûm eder. eslint-plugin-react-hooks'un exhaustive-deps kuralı eksik beyanı yakalar; onu susturmak değil dinlemek kıdem işaretidir: lint'e itiraz ettiğin her yerde asıl sorun genelde effect'in tasarımıdır. İki incelik:

  • Kıyas Object.is iledir — 2. dersin sözleşmesi burada da geçerli: her render'da yeniden yaratılan nesne/dizi/fonksiyon bağımlılığı "hep değişmiş" sayılır (04. bölümün döngü tuzağı buradan doğar).
  • set fonksiyonları (setFiltre gibi) kimliği sabittir — listeye yazılmaları gerekmez (lint de istemez).

04İki klasik tuzak

Tuzak 1 — stale closure (bayat kapanış). Effect'in fonksiyonu, kurulduğu render'ın fotoğrafını kapatır (8. hattın 14. dersindeki closure). Fotoğraf eskiyince effect yalan söyler:

// ❌ sayaç 1'de takılır
useEffect(() => {
  const id = setInterval(() => {
    setSaniye(saniye + 1);   // closure'daki saniye SONSUZA DEK 0
  }, 1000);
  return () => clearInterval(id);
}, []);   // boş liste: effect bir kez kuruldu, fotoğraf hiç tazelenmedi

// ✅ çözüm: fotoğrafa değil deftere sor — bağımlılık da gerekmez
useEffect(() => {
  const id = setInterval(() => setSaniye(s => s + 1), 1000);
  return () => clearInterval(id);
}, []);

Çözüm hiyerarşisi: önce fonksiyonel güncelleme (2. ders) — bağımlılığı kökten kaldırır; olmuyorsa değeri bağımlılığa yaz ve temizliğin kur-yık döngüsüne güven (interval her değişimde yeniden kurulur — çoğu zaman doğru davranıştır).

Tuzak 2 — sonsuz döngü. İki üretim biçimi var:

// ❌ biçim 1: effect kendi bağımlılığını değiştiriyor
useEffect(() => {
  setGorunen(basvurular.filter(b => b.unvan.includes(filtre)));
}, [basvurular, filtre, gorunen]);   // gorunen'i set edip gorunen'i dinliyor

// ❌ biçim 2: bağımlılık her render'da yeni referans
const secenekler = { filtre, siralama };            // her render'da YENİ nesne
useEffect(() => { yukle(secenekler); }, [secenekler]);  // "hep değişti"

Biçim 1'in gerçek çözümü effect'i silmektir — türetilen değer render'da hesaplanır (2. dersin kuralı; 07. bölümde genelleyeceğiz). Biçim 2'nin çözümü bağımlılığı ilkellere daraltmaktır: [filtre, siralama] — nesneyi effect'in içinde kur. (Fonksiyon bağımlılıkları için aynı derdin ilacı useCallback'tir — 6. ders senedi.)

05Temizlik: kur-yık sözleşmesi ve StrictMode

Effect'ten döndürülen fonksiyon temizliktir: React onu, effect yeniden çalışmadan önce ve bileşen ağaçtan sökülürken çağırır. ngOnDestroy'un karşılığı — ama daha sık çalışır, çünkü sözleşme "ölürken" değil "her yeni kurulumdan önce eskisini yık"tır:

useEffect(() => {
  const soket = yeniBildirimKanali(kullaniciId);   // KUR
  soket.baglan();
  return () => soket.kapat();                      // YIK — sonraki kurulumdan önce
}, [kullaniciId]);
// kullaniciId değişince sıra: eski kanal kapat → yeni kanal aç
// bileşen sökülünce: kanal kapat. Sızıntı imkânsız.

Temizlik isteyen her şeyi reflekse bağla: setInterval → clearInterval, addEventListener → removeEventListener, abonelik → iptal, fetch → abort (06. bölüm). Unutulan temizlik iki yüzle gelir: bellek sızıntısı (sökülen bileşenin dinleyicileri yaşar) ve hayalet güncelleme ("unmounted component" uyarıları, üst üste binen interval'lar).

StrictMode şimdi anlam kazanıyor: geliştirmede React her effect'i bir kez fazladan kur-yık-kur çalıştırır. Bu bir bug değil, temizliğinin sınavıdır: kur-yık simetrisi doğruysa çift çalıştırmayı fark etmezsin bile; "veri iki kez çekiliyor, StrictMode'u kapatayım" diyen kod, aslında temizliği (iptali) eksik olan koddur. Sınavı kapatmak yerine geçmek gerekir.

06Veri çekme ve yarış: AbortController geri dönüyor

Effect'te veri çekmenin saf hâli üç eksikle doğar: yükleniyor/hata durumu yönetimi (5. derste tip güvenli hâlini kuracağız), önbellek — ve en sinsisi, yarış durumu: kullanıcı filtreyi hızla iki kez değiştirdi; ilk istek yavaş, ikincisi hızlı döndü; sonra ilk istek dönüp taze ekranı bayat veriyle ezdi. Angular'da bu derdi switchMap'e devretmiştin (10. hattın 5. dersi); React'te elle çözersin — 8. hattın 17. dersindeki AbortController tam buraya geri döner:

useEffect(() => {
  const ac = new AbortController();

  async function yukle() {
    try {
      const yanit = await fetch(`/api/basvurular?durum=${filtre}`,
                                { signal: ac.signal });
      if (!yanit.ok) throw new Error(`HTTP ${yanit.status}`);
      setBasvurular(await yanit.json());
    } catch (hata) {
      if (hata instanceof DOMException && hata.name === "AbortError") return;
      // iptal hata değildir — sessizce çık; gerçek hatayı state'e yaz (5. ders)
      setHata(true);
    }
  }

  yukle();
  return () => ac.abort();   // filtre değişti → ESKİ istek iptal
}, [filtre]);

Temizlik + iptal ikilisi üç kuşu birden vurur: yarış biter (eski istek asla set çağıramaz), sökülen bileşene yazma biter, StrictMode sınavı geçer (ilk kurulumun isteği anında iptal edilir). Dürüst not: üretimde bu kalıbı her bileşende elle yazmazsın — önbellek, yeniden deneme ve paylaşımıyla birlikte bu işin sahibi TanStack Query'dir (10. ders); ama kütüphanenin ne çözdüğünü bilmek, onu doğru kullanmanın ön şartıdır ve mülakatta "effect'te fetch'in nesi eksik?" sorusunun cevabıdır.

07Effect son çare: girmeyecek üç iş

10. hattın 9. dersindeki ilkenin React hâli. Effect yalnız React dışı bir sistemle senkron için vardır (ağ, tarayıcı API'si, üçüncü parti kütüphane, abonelik). Şu üç iş sınıfı effect'e girdiyse tasarım kokusu var:

KokuYanlışDoğru
Türetilen değerState'ten state hesaplayan effect (04. bölümün döngüsü)Render'da hesapla; pahalıysa useMemo (6. ders)
Olaya ait iş"Gönderildi" state'ini dinleyip bildirim atan effectİşi olayın işleyicisinde yap — gönderen handler bildirimi de atar
State'e tepki zinciriA değişince B'yi, B değişince C'yi set eden effect'lerZinciri tek olay işleyicisinde ya da useReducer geçişinde topla (6. ders)

Turnusol sorusu: "bu iş hangi olayın sonucu?" Cevap bir kullanıcı olayıysa (tıklama, gönderim) yeri o olayın işleyicisidir; cevap "şu verinin görünmesi"yse render'dır; cevap gerçekten "React dışı bir sistemi güncel tutmak"sa — ancak o zaman effect. Effect sayısı azaldıkça uygulama öngörülebilir hâle gelir; "az effect, çok olay işleyicisi" tasarım hedefidir.

08Mini alıştırma — Dört tuzağın turu

Yaklaşık 20 dakika · kurulum yok — 2. dersteki StackBlitz projesinin üstüne kur

  1. Başlığı senkronla: document.title'a görünen başvuru sayısını yazan effect kur; bağımlılığı önce boş bırakıp bayatladığını gör, sonra doğru beyan et.
  2. Stale closure'ı yaşa: "başvuru süresi" sayacı kur — setInterval içinde setSaniye(saniye + 1) ve boş bağımlılık; 1'de takıldığını gör. Fonksiyonel güncellemeyle düzelt; iki hâli yorumla karşılaştır.
  3. Sonsuz döngü üret: bağımlılığa her render'da yeni kurulan bir secenekler nesnesi koy ve effect içinde console.count ile patlamayı say; ilkellere daraltıp düzelt.
  4. Temizliği kanıtla: effect'te window'a resize dinleyicisi ekle, temizlikte kaldır. Temizliği bilerek silip StrictMode'da dinleyicilerin biriktiğini konsolla göster; geri koy — çift çalıştırmanın artık iz bırakmadığını gör.
  5. Yarışı sahnele: filtre'ye bağlı sahte bir yükleme yaz (fetch yerine rastgele gecikmeli setTimeout + Promise); filtreyi hızla değiştirip bayat sonucun taze ekranı ezdiğini gör. Temizlikte iptal bayrağı / AbortController ekleyip yarışı bitir.
  6. Bir effect sil: "filtre değişince gorunen'i set eden" effect'i bilerek yaz, 04. bölümdeki kokuyu teşhis et, effect'i silip hesabı render'a taşı. "Bu iş hangi olayın sonucu?" sorusunu üç örneğe uygula ve cevaplarını not et.

09Kendini sına

useEffect ne zaman çalışır ve ne için vardır? "Mount olunca çalıştır" okuması neden eksiktir — Angular yaşam döngüsüyle farkını anlat.

Effect, render ve commit bittikten — ekran güncellendikten — sonra çalışır; render fonksiyonunun saflığını bozmadan React dışı dünyaya (ağ, tarayıcı API'si, abonelik, üçüncü parti kütüphane) dokunmanın kapısıdır. Doğru zihin modeli "bileşen kuruldu, şunu yap" değil, "şu reaktif değerlerle dış sistemi senkron tut"tur: bağımlılıklar değiştikçe React önce eski kurulumun temizliğini çalıştırır, sonra effect'i yeniden kurar — yani effect bir kereye mahsus bir tören değil, kur-yık döngüsü olan bir senkronizasyon sözleşmesidir. Angular'ın ngOnInit/ngOnDestroy'u "doğum/ölüm" anlarını işaretler; useEffect ise ikisini tek sözleşmede birleştirir ve arada da (her bağımlılık değişiminde) çalışır. "Mount okuması"nın pratik zararı: boş bağımlılıkla kurulmuş effect'in fotoğrafının bayatlayacağını (stale closure) ve StrictMode'un kur-yık-kur sınavının neden meşru olduğunu görememektir.

"Bağımlılık listesi seçim değil beyandır" ne demek? Stale closure ve sonsuz döngü tuzaklarını çözüm kalıplarıyla anlat.

Effect'in içinde okunan her reaktif değer (props, state, türevleri) listede olmak zorundadır — liste "ne sıklıkta çalışsın" ayarı değil, "bu effect şu değerlere bağlı" gerçeğinin beyanıdır; değer gizlemek effect'i eski render'ın fotoğrafına mahkûm eder. Stale closure: boş bağımlılıkla kurulan interval, closure'ında ilk render'ın saniye = 0'ını kapatır ve sayaç 1'de takılır. Çözüm hiyerarşisi: önce fonksiyonel güncelleme (setSaniye(s => s + 1) — bağımlılık ihtiyacını kökten kaldırır), olmuyorsa değeri bağımlılığa yazıp kur-yık döngüsüne güvenmek. Sonsuz döngü iki biçimde üretilir: effect kendi set ettiği state'i dinler (gerçek çözüm effect'i silip türetilen değeri render'da hesaplamak) ya da bağımlılık her render'da yeni referans alan nesne/dizi/fonksiyondur — Object.is kıyası onu hep "değişmiş" görür; çözüm bağımlılığı ilkellere daraltmak, nesneyi effect içinde kurmak (fonksiyon için useCallback). Lint'in exhaustive-deps kuralına itiraz ettiğin yerde suçlu genelde liste değil, effect'in tasarımıdır.

Temizlik fonksiyonu ne zaman çağrılır? StrictMode effect'leri neden iki kez çalıştırır ve "veri iki kez çekiliyor" şikâyetinin doğru çözümü nedir?

Temizlik iki anda çağrılır: effect yeniden çalışmadan hemen önce (eski kurulum yıkılır, yenisi kurulur) ve bileşen ağaçtan sökülürken. Sözleşme "ölürken çalış" değil, "her yeni kurulumdan önce eskisini yık"tır — bu yüzden kullaniciId değişiminde eski soket kapanıp yenisi açılır ve sızıntı imkânsızlaşır. Refleks çiftleri: interval→clear, listener→remove, abonelik→iptal, fetch→abort. Unutulan temizliğin iki yüzü: bellek sızıntısı (sökülmüş bileşenin dinleyicileri yaşar) ve hayalet güncellemeler. StrictMode geliştirmede her effect'i bilerek kur-yık-kur çalıştırır: bu, kur-yık simetrisinin sınavıdır — temizlik doğruysa çift çalıştırma iz bırakmaz. "Veri iki kez çekiliyor" şikâyetinin doğru çözümü StrictMode'u kapatmak değil, temizliğe iptali eklemektir (AbortController + abort): ilk kurulumun isteği anında iptal edilir, ağ köşesinde çift istek görünse de state'e tek sonuç yazılır ve aynı iptal, üretimdeki gerçek yarış durumunu da çözer.

"Effect son çare" ilkesini aç: hangi işler effect'e girmez, effect'in tek meşru işi nedir? Veri çekmede effect'in eksikleri nelerdir?

Effect'in tek meşru işi React dışı bir sistemi state ile senkron tutmaktır: ağ, tarayıcı API'si (başlık, localStorage, event listener), abonelik, üçüncü parti widget. Girmemesi gereken üç sınıf: (1) türetilen değer — state'ten state hesaplayan effect (filtre değişince görüneni set etmek) fazladan render, senkron borcu ve döngü riski üretir; hesap render'da yapılır, pahalıysa useMemo. (2) Olaya ait iş — "gönderildi state'ini dinleyip bildirim atmak" değil, gönderen olay işleyicisinde bildirimi de atmak; turnusol soru "bu iş hangi olayın sonucu?"dur. (3) State'e tepki zincirleri — A→B→C set eden ardışık effect'ler; geçişler tek olay işleyicisinde ya da reducer'da toplanır. Bu, Angular'da signal effect'i için öğrenilen ilkenin aynısıdır. Veri çekmede saf effect'in eksikleri: yükleniyor/hata durumunu elle yönetirsin, önbellek ve istek paylaşımı yoktur, yarış durumunu (AbortController/iptal bayrağı — Angular'da switchMap'in işi) elle çözersin; üretimdeki sahibi TanStack Query'dir ama kütüphanenin ne çözdüğünü bilmek doğru kullanmanın ön şartıdır.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) useEffect nedir; ne zaman çalışır? (2) Bağımlılık dizisinin üç biçimi nedir; boş dizi ne anlama gelir? (3) Stale closure nedir; nasıl çözülür? (4) Effect'te sonsuz döngü hangi durumlarda oluşur? (5) Temizlik (cleanup) fonksiyonu ne zaman çağrılır; hangi işlerde zorunludur? (6) StrictMode effect'leri neden iki kez çalıştırır? (7) Veri çekmede yarış durumu (race condition) nedir; AbortController ile nasıl önlenir? (8) Hangi işler effect'e yazılmamalıdır? (9) exhaustive-deps lint kuralı neyi denetler? (10) useEffect ile ngOnInit/ ngOnDestroy arasındaki kavramsal fark nedir?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Tanım — "Render safttır; dış dünyaya dokunuş useEffect'te, render ve commit'ten sonra çalışır. Doğru okuma 'mount'ta çalıştır' değil, 'React dışı sistemi state'le senkron tut'tur." (2) Beyan — "Bağımlılık listesi seçim değil beyandır: içeride okunan her reaktif değer listededir; exhaustive-deps'i susturmam, dinlerim." (3) Tuzaklar — "Stale closure'ı fonksiyonel güncellemeyle, döngüyü bağımlılığı ilkele daraltarak ya da effect'i silerek çözerim." (4) Sözleşme — "Temizlik her yeniden kurulumdan önce çalışır; interval-listener-abonelik-fetch dörtlüsünün yıkımı reflekstir; StrictMode'un çift çalıştırması bu simetrinin sınavıdır — kapatılmaz, geçilir." (5) Kıdem vuruşu — "Fetch'te yarışı AbortController'la keserim — Angular'da switchMap'in yaptığı iş; üretimde bu katmanın sahibi TanStack Query'dir. Ve effect son çaredir: türetilen değer render'da, olayın işi handler'da; az effect, çok olay işleyicisi."