İçeriğe geç

useEffect — Bağımlılıklar ve Temizlik

Orta 10 dk Çok sık karşılaşılır

Önce şunu oku: React Render Modeli

30 saniyede özet

useEffect'te iki ayrı hata yapılabilir: eksik bağımlılık eski veriyi gösterir, eksik temizlik açık bağlantı bırakır. Çoğu kişi birini fark edip ötekini düzeltir.

Konsoldaki bir uyarıyı susturmak için küçük bir değişiklik yaptın ve sorun çözüldü sandın. Uyarı sustu — ama haklıydı.

O değişiklik, effect’in bağımlılık dizisini boş bırakmaktı: useEffect(..., []).

  1. Bayt: Effect'in dizisini boş bıraktım, uyarı sustu. Bir kez çalışıyor, tertemiz!

  2. Sen: Kullanıcı değişince ne oluyor peki?

  3. Bayt: Ekranda yeni kullanıcı var ama gelen mesajlar hâlâ eskisinin...

  4. Bayt: Cebimdeki fotoğraf eski kalmış olabilir mi?

Effect çalıştığı anın fotoğrafını cebine koyar.
Adım adım oku
  1. Effect ilk render'da çalışır ve o anki değerleri, burada userId 1'i, yanına alır.
  2. Dizi boşsa userId 2 olsa bile effect yeniden çalışmaz; cebindeki fotoğraf eski kalır.
  3. Effect yeniden çalışsa ama temizlik yazılmamış olsa, eski abonelik kapanmaz ve ikisi birden açık kalır.
  4. Dizide userId ve bir temizlik varsa sıra hep aynıdır: önce eski abonelik kapanır, sonra yenisi açılır.

Bağımlılık dizisi ne yapar?

React her render’dan sonra diziyi bir öncekiyle karşılaştırır. Hepsi aynıysa effect’i atlar.

Karşılaştırma Object.is ile yapılır — yani referansa bakar, içeriğe değil. Bu tek cümle, aşağıdaki hataların çoğunu açıklar.

YazımNe zaman çalışır
useEffect(fn)Her render’dan sonra
useEffect(fn, [])Yalnızca mount’ta
useEffect(fn, [userId])Mount’ta ve userId değiştiğinde

Dizi yazmamak ile boş dizi yazmak taban tabana zıttır. İkisi de nadiren doğru cevaptır.

Kafam karıştı, daha basit anlat

Bağımlılık dizisi bir kontrol listesidir. React her çizimden sonra listeye bakar: bir şey değiştiyse effect’i yeniden çalıştırır, hiçbir şey değişmediyse atlar.

Kendin gör

useEffect — bağımlılık dizisi, temizlik ve StrictMode

Tohum 2
useEffect(() => {
  const sub = chat.subscribe(userId);
}, []);

Yaşam döngüsü — adım 0/5

    Effect / temizlik

    0 / 0

    dengeli

    Açık abonelik

    0

    temiz

    Abone olunan userId

    —/ güncel 1

    güncel · 0 atlanan

    Hız
    Adım 0

    Şu an ne oldu?

    Bileşen henüz mount edilmedi

    Effect render sırasında değil, render ekrana yansıdıktan sonra çalışır. Bu yüzden ilk boyama hiçbir zaman effect’i beklemez.

    Aklında kalsın: useEffect render’ın parçası değildir; React ekranı güncelledikten sonra çalışan bir yan etkidir.

    Olay günlüğü (0)

    Henüz olay yok. Oynat veya adımla.

    Sırayla dene:

    1. [] + temizlikEffect'in döndürdüğü fonksiyon. Unmount'ta **ve** effect her yeniden çalışmadan önce koşar; ikincisi en çok atlanan kısımdır.Sözlükte gör → yok. Sonuna kadar oynat. İki hata birden: userId 2 olduğu hâlde abonelik hâlâ 1’de (bayat), ve unmount’ta bağlantı açık kaldı (sızıntı).
    2. Temizliği aç, dizi [] kalsın. Sızıntı bitti — ama bayatlık duruyor. Bunlar farklı hatalar.
    3. Diziyi [userId] yap. İkisi de bitti. Effect 2 kez çalıştı, temizlik 2 kez — sayılar eşit.
    4. [options]’a geç. Effect her render’da çalışıyor, çünkü nesne her render’da yeniden oluşuyor.
    5. Temizliği kapat, StrictMode’u aç. Sızıntı ilk render’da görünür oluyor.
    Hızlı kontrolBaşlangıç

    `useEffect`'in bağımlılık dizisi boş olursa (`[]`) ne olur?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    İki farklı hata

    Bayat kapanış — eksik bağımlılık

    Effect gövdesi, çalıştığı andaki değerleri kapatır (closureBir fonksiyonun, tanımlandığı andaki değişkenleri yanında taşıması. Effect yeniden çalışmazsa eski değerleri taşımaya devam eder — bayat closure hatası budur.Sözlükte gör →). Yeniden çalışmazsa o eski değerleri kullanmaya devam eder.

    Bayat
    useEffect(() => {
    const sub = chat.subscribe(userId); // mount'taki userId
    return () => sub.close();
    }, []); // userId değişse bile çalışmaz

    Belirti: ekranda yanlış kullanıcının verisi, tıklamanın hiçbir şey yapmaması, sayaç hep aynı sayıyı göstermesi.

    Sızıntı — eksik temizlik

    Sızdıran
    useEffect(() => {
    const sub = chat.subscribe(userId);
    // return yok
    }, [userId]);

    Bağımlılıklar doğru, effect doğru zamanlarda çalışıyor — ama her çalışma yeni bir abonelik açıyor ve eskisi kapanmıyor.

    Belirti: zamanla artan bellek, kapanmış bir bileşene gelen güncellemeler, aynı isteğin birden çok kez gönderilmesi.

    Kafam karıştı, daha basit anlat

    Listeye bir değeri yazmayı unutursan, effect o değerin eski fotoğrafıyla çalışmaya devam eder. Değer değişir, effect bundan haberdar olmaz.

    Hızlı kontrolOrta

    Bu bileşende butona bir kez basıldıktan 1 saniye sonra ne yazdırılır?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
    BayatDeger.tsx
    1function Sayac() {
    2 const [count, setCount] = useState(0);
    3
    4 useEffect(() => {
    5 const id = setInterval(() => {
    6 console.log("count:", count);
    7 }, 1000);
    8 return () => clearInterval(id);
    9 }, []);
    10
    11 return <button onClick={() => setCount(count + 1)}>{count}</button>;
    12}
    TypeScript JSXUTF-8LF

    Temizlik ne zaman çalışır?

    Effect userId için bir abonelik açıyor ve temizliğinde kapatıyor. userId 1'den 2'ye değişti. Bileşen ekranda kalıyor. Temizlik çalışır mı? Cevabı göster

    Evet, hem de yeni effect’ten önce. Önce eski abonelik kapanır, sonra yenisi açılır.

    Yaygın yanılgı: “temizlik unmount’ta çalışır.” Eksik.

    Temizlik her yeniden çalışmadan önce de çalışır. Sıra her zaman aynıdır:

    userId 1 → 2 değişti
    1. eski effect'in temizliği sub.close() ← önce bu
    2. yeni effect subscribe(2) ← sonra bu

    Bu sıra, iki aboneliğin aynı anda açık olmasını engeller. Bilmek bağımlılık hatalarının yarısını önler.

    Bir bankanın döviz kuru ekranında bu sırayı canlı görebilirsin. Kullanıcı dolardan euroya geçince neyin kapanıp neyin açıldığına birlikte bakalım.

    Derinleş · Canlı döviz kuru: çift değişince akış da değişir 4 dosya · ~77 satır · ilk okumada atlayabilirsin
    Proje dosyaları

    src/features/fx/ fxFeed.ts Bir döviz çifti için akış açar ve onu kapatan fonksiyonu geri verir; kurlar metin olarak kalır.

    src/features/fx/fxFeed.ts
    export type CurrencyPair = 'USDTRY' | 'EURTRY' | 'GBPTRY';
    export interface FxQuote {
    readonly pair: CurrencyPair;
    readonly bid: string; // decimal string such as "32.4105", never turned into a float
    readonly ask: string;
    }
    function isQuoteFor(pair: CurrencyPair, value: unknown): value is FxQuote {
    return (
    typeof value === 'object' && value !== null &&
    'pair' in value && value.pair === pair &&
    'bid' in value && typeof value.bid === 'string' &&
    'ask' in value && typeof value.ask === 'string'
    );
    }
    // Opens one server-sent event stream for a pair. The returned function closes it.
    export function subscribeToRate(pair: CurrencyPair, onQuote: (quote: FxQuote) => void): () => void {
    const source = new EventSource(`/api/fx/stream?pair=${pair}`);
    source.onmessage = (event) => {
    const data: unknown = JSON.parse(event.data);
    if (isQuoteFor(pair, data)) onQuote(data);
    };
    return () => source.close();
    }

    src/features/fx/ useFxRate.ts Hook, çift değişince önce eski akışı kapatır, sonra yenisini açar.

    src/features/fx/useFxRate.ts
    import { useEffect, useState } from 'react';
    import { subscribeToRate, type CurrencyPair, type FxQuote } from './fxFeed';
    export function useFxRate(pair: CurrencyPair): FxQuote | null {
    const [quote, setQuote] = useState<FxQuote | null>(null);
    useEffect(() => {
    // The cleanup runs before the next pair subscribes and on unmount: one stream at a time.
    return subscribeToRate(pair, setQuote);
    }, [pair]);
    // Until the new pair's first quote lands, show nothing rather than the old pair's rate.
    return quote?.pair === pair ? quote : null;
    }

    src/features/fx/ FxTicker.tsx Kur ekranı yalnızca çifti seçer ve hook'tan gelen kuru gösterir.

    src/features/fx/FxTicker.tsx
    import { useState } from 'react';
    import type { CurrencyPair } from './fxFeed';
    import { useFxRate } from './useFxRate';
    const PAIRS: readonly CurrencyPair[] = ['USDTRY', 'EURTRY', 'GBPTRY'];
    export function FxTicker() {
    const [pair, setPair] = useState<CurrencyPair>('USDTRY');
    const quote = useFxRate(pair);
    return (
    <section aria-live="polite">
    {PAIRS.map((p) => (
    <button key={p} type="button" aria-pressed={p === pair} onClick={() => setPair(p)}>
    {p}
    </button>
    ))}
    <p>{quote ? `Alış ${quote.bid} · Satış ${quote.ask}` : 'Kur bekleniyor…'}</p>
    </section>
    );
    }

    src/features/fx/ useFxRate.alternative.ts Şöyle de yazılabilirdi: boş dizi, temizlik yok. Bak, EURTRY seçilince ekranda hâlâ dolar kuru akıyor.

    src/features/fx/useFxRate.alternative.ts
    import { useEffect, useState } from 'react';
    import { subscribeToRate, type CurrencyPair, type FxQuote } from './fxFeed';
    // Another way to write it: the lint warning is silenced and the first screen looks fine.
    export function useFxRate(pair: CurrencyPair): FxQuote | null {
    const [quote, setQuote] = useState<FxQuote | null>(null);
    useEffect(() => {
    // Captures the first pair only: after a switch to EURTRY this still streams USDTRY.
    subscribeToRate(pair, setQuote);
    // No cleanup is returned, so the stream stays open after the ticker unmounts.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    return quote;
    }
    Kafam karıştı, daha basit anlat

    Temizlik yalnızca en sonda değil, effect her yeniden çalışmadan önce de çalışır. Önce eskisini toplar, sonra yenisini kurar.

    Hızlı kontrolBaşlangıç

    Effect'in döndürdüğü temizlik fonksiyonu ne zaman çalışır?

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    Her belirtiyi hangi hatanın ürettiğine göre sınıflandır.

    Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

    Sınıflandırılmamış

    Eksik bağımlılık

    Effect çalışması gerekirken çalışmadı

      Eksik temizlik

      Açılan şey kapanmadı

        Tuzaklar: her seferinde yeni nesne

        Bu kod, bağımlılık dizisi dolu olmasına rağmen her render’da çalışır:

        const options = { id: userId }; // her render'da yeni nesne
        useEffect(() => {
        const sub = chat.subscribe(options);
        }, [options]); // referans her zaman farklı

        Aynı sorun fonksiyonlarda ve dizilerde de var. Üç çözüm:

        • Primitifi doğrudan koy. [userId] — en basit ve en iyi çözüm.
        • useMemo / useCallback ile referansı sabitle — gerçekten nesne gerekiyorsa.
        • Nesneyi effect’in içine taşı — dışarıda tutulmasına gerek yoksa.

        İlk seçenek neredeyse her zaman doğrudur. Diğer ikisi, birinci mümkün değilse gelir.

        StrictMode neden iki kez çalıştırıyor. Geliştirmede React bileşeni mount eder, hemen kaldırır ve tekrar mount eder. Bu bir hata değil, bir testtir.

        Temizlik doğru yazılmışsa hiçbir şey değişmez: bir abonelik açılır, kapanır, bir tane daha açılır — sonuçta yine tek abonelik. Temizlik yoksa iki abonelik açık kalır ve sorun ilk render’da görünür.

        Bu davranış üretim derlemesinde yoktur. “StrictMode’u kapatalım” demek, testi kapatıp hatayı üretime bırakmaktır.

        Hızlı kontrolOrta

        Bir değeri bağımlılık dizisinden çıkarmak (lint uyarısını bastırmak) neden tehlikeli?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Fetch için doğru kalıp. En sık karşılaşılan durum: bir effect içinde veri çekmek ve yarış durumundan (race conditionSonucun thread'lerin hangi sırayla çalıştığına bağlı olması. Kod çoğu zaman doğru çalışır, sonra bir gün çalışmaz.Sözlükte gör →) kaçınmak.

        Yarış durumunu kapatan kalıp
        useEffect(() => {
        const controller = new AbortController();
        fetch(`/api/users/${userId}`, { signal: controller.signal })
        .then((res) => res.json())
        .then(setUser)
        .catch((err) => {
        if (err.name !== 'AbortError') setError(err);
        });
        return () => controller.abort();
        }, [userId]);

        Temizlik olmadan şu olur: userId hızlıca 1’den 2’ye değişir, iki istek uçar, 1’in cevabı sonra gelirse ekranda yanlış kullanıcı kalır. abort() bunu kapatır.

        Pratikte bu işi React Query gibi bir kütüphaneye devretmek daha iyidir — ama neyi devrettiğini bilmeden devretmek, hata çıktığında seni çaresiz bırakır.

        Kendini sına

        Şimşek turu1/5

        Boş bağımlılık dizisi, effect'in ilk render'daki değerleri kullanmaya devam etmesine yol açabilir.

        Soru 1/3Orta

        Bu bileşende butona üç kez basıldıktan 3 saniye sonra konsolda ne görünür?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
        Counter.tsx
        1function Counter() {
        2 const [count, setCount] = useState(0);
        3
        4 useEffect(() => {
        5 const id = setInterval(() => {
        6 console.log(count);
        7 }, 1000);
        8
        9 return () => clearInterval(id);
        10 }, []);
        11
        12 return <button onClick={() => setCount(count + 1)}>{count}</button>;
        13}
        TypeScriptUTF-8LF

        Aklında kalacak üç şey

        1. 1 Boş dizi 'bir kez çalış' demek değil, 'ilk render'ın değerlerini sonsuza kadar kullan' demektir. Eski değerin takılı kalması buradan gelir.
        2. 2 Temizlik yalnızca bileşen kaldırılırken çalışmaz, her yeniden çalışmadan önce de çalışır. Sıra hep aynıdır: önce eski temizlik, sonra yeni effect.
        3. 3 İki ayrı hata var: eksik bağımlılık eski veri gösterir, eksik temizlik bağlantı sızdırır. Belirtileri de çözümleri de farklıdır.
        Sonraki kapı Kartı left ile kaydırınca takılıyor, transform ile kaydırınca akıyor. Aradaki fark hangi adımda? Tarayıcı Nasıl Çizer — Animasyon Neden Takılıyor? · 9 dk

        4 kart sonraki derste seni bekliyor

        0/5 kart bu dersten toplandı

        Bu dersin üstüne kurulanlar

        Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.