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 (
ngOnDestroysimetrisi) veStrictModeç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çim | Ne zaman çalışır | Tipik 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.isiledir — 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). setfonksiyonları (setFiltregibi) 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:
| Koku | Yanlış | Doğru |
|---|---|---|
| Türetilen değer | State'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 zinciri | A değişince B'yi, B değişince C'yi set eden effect'ler | Zinciri 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
- 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. - Stale closure'ı yaşa: "başvuru süresi" sayacı kur —
setIntervaliçindesetSaniye(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. - Sonsuz döngü üret: bağımlılığa her render'da yeni kurulan bir
seceneklernesnesi koy ve effect içindeconsole.countile patlamayı say; ilkellere daraltıp düzelt. - Temizliği kanıtla: effect'te
window'aresizedinleyicisi 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. - Yarışı sahnele:
filtre'ye bağlı sahte bir yükleme yaz (fetchyerine rastgele gecikmelisetTimeout+ Promise); filtreyi hızla değiştirip bayat sonucun taze ekranı ezdiğini gör. Temizlikte iptal bayrağı /AbortControllerekleyip yarışı bitir. - 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."
