Efekt Bağımlılıklarını Kaldırma

Bir Efekt yazdığınızda, linter, Efektin okuduğu her reaktif değeri (props ve state gibi) Efektinizin bağımlılıkları listesine dahil ettiğinizi doğrular. Bu, Efektinizin bileşeninizin en son prop’ları ve state’i ile senkronize kalmasını sağlar. Gereksiz bağımlılıklar, Efektinizin çok sık çalışmasına ve hatta sonsuz bir döngü oluşturmasına neden olabilir. Gereksiz bağımlılıkları gözden geçirmek ve Efektlerinizden kaldırmak için bu kılavuzu izleyin.

Bunları öğreneceksiniz

  • Sonsuz efekt bağımlılık döngüleri nasıl düzeltilir
  • Bir bağımlılığı kaldırmak istediğinizde ne yapmalısınız
  • Efektinizden bir değeri ona “tepki vermeden” nasıl okuyabilirsiniz
  • Nesne ve fonksiyon bağımlılıklarından nasıl ve neden kaçınılır
  • Bağımlılık linterini bastırmak neden tehlikelidir ve bunun yerine ne yapılmalıdır

Bağımlılıklar kod ile eşleşmelidir

Bir Efekt yazdığınızda, ilk olarak Efektinizin yapmasını istediğiniz şeyi nasıl başlatacağınızı ve durduracağınızı belirtirsiniz:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
// ...
}

Ardından, efekt bağımlılıklarını boş bırakırsanız ([]), linter doğru bağımlılıkları önerecektir:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // <-- Buradaki hatayı düzeltin!
  return <h1>{roomId} odasına hoş geldiniz!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyehat">Seyehat</option>
          <option value="müzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Bunları linterin söylediğine göre doldurun:

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...
}

Efektler reaktif değerlere “tepki verir” roomId reaktif bir değer olduğundan (yeniden renderlama nedeniyle değişebilir), linter bunu bir bağımlılık olarak belirttiğinizi doğrular. Eğer roomId farklı bir değer alırsa, React Efektinizi yeniden senkronize edecektir. Bu, sohbetin seçilen odaya bağlı kalmasını ve açılır menüye “tepki vermesini” sağlar:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
  return <h1>{roomId} odasına hoş geldiniz!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyehat">Seyahat</option>
          <option value="müzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Bir bağımlılığı kaldırmak için, bunun bir bağımlılık olmadığını kanıtlayın

Efektinizin bağımlılıklarını “seçemeyeceğinize” dikkat edin. Efektinizin kodu tarafından kullanılan her reaktif değer bağımlılık listenizde bildirilmelidir. Bağımlılık listesi çevredeki kod tarafından belirlenir:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) { // Bu reaktif bir değer
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Bu efekt o reaktif değeri okur
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Dolayısıyla, bu reaktif değeri Efektinizin bir bağımlılığı olarak belirtmeniz gerekir
// ...
}

Reaktif değerler prop’ları ve doğrudan bileşeninizin içinde bildirilen tüm değişkenleri ve fonksiyonları içerir. roomId reaktif bir değer olduğundan, bağımlılık listesinden kaldıramazsınız. Linter buna izin vermez:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect'in eksik bir bağımlılığı var: 'roomId'
// ...
}

Ve linter haklı olacaktır! Zaman içinde roomId değişebileceğinden, bu durum kodunuzda bir hataya yol açacaktır.

Bir bağımlılığı kaldırmak için, linter’e bağımlılık olmasına gerek olmadığını “kanıtlayın.” Örneğin, reaktif olmadığını ve yeniden render edildiğinde değişmeyeceğini kanıtlamak için roomId’yi bileşeninizin dışına taşıyabilirsiniz:

const serverUrl = 'https://localhost:1234';
const roomId = 'music'; // Artık reaktif bir değer değil

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Tüm bağımlılıklar bildirildi
// ...
}

Artık roomId reaktif bir değer olmadığından (ve yeniden render edilmede değişemeyeceğinden), bir bağımlılık olmasına gerek yoktur:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';
const roomId = 'müzik';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>{roomId} odasına hoş geldiniz!</h1>;
}

Bu nedenle artık boş ([]) bağımlılık listesi belirtebilirsiniz. Efektiniz gerçekten artık herhangi bir reaktif değere bağlı değildir, bu nedenle gerçekten bileşenin herhangi bir prop’u veya state’i değiştiğinde yeniden çalıştırılması gerekmez.

Bağımlılıkları değiştirmek için kodu değiştirin

İş akışınızda bir düzen fark etmiş olabilirsiniz:

  1. İlk olarak, Efektinizin kodunu veya reaktif değerlerinizin nasıl beyan edildiğini değiştirirsiniz.
  2. Ardından, linter’ı takip eder ve bağımlılıkları değiştirdiğiniz kodla eşleşecek şekilde ayarlarsınız.
  3. Bağımlılıklar listesinden memnun değilseniz, ilk adıma geri dönersiniz (ve kodu tekrar değiştirirsiniz).

Son kısım önemlidir. Bağımlılıkları değiştirmek istiyorsanız, önce çevredeki kodu değiştirin. Bağımlılık listesini Efekt kodunuz tarafından kullanılan tüm reaktif değerlerin bir listesi olarak düşünebilirsiniz. Bu listeye ne koyacağınızı seçmezsiniz. Liste kodunuzu tanımlar. Bağımlılık listesini değiştirmek için kodu değiştirin.

Bu bir denklem çözmek gibi gelebilir. Bir hedefle başlayabilirsiniz (örneğin, bir bağımlılığı kaldırmak için) ve bu hedefle eşleşen kodu “bulmanız” gerekir. Herkes denklem çözmeyi eğlenceli bulmaz ve aynı şey Efekt yazmak için de söylenebilir! Neyse ki, aşağıda deneyebileceğiniz yaygın tariflerin bir listesi var.

Tuzak

Mevcut bir kod tabanınız varsa, linter’ı bu şekilde bastıran bazı Efektleriniz olabilir:

useEffect(() => {
// ...
// 🔴 Linter'i şu şekilde bastırmaktan kaçının:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

Bağımlılıklar kodla eşleşmediğinde, hataların ortaya çıkma riski çok yüksektir. Linter’ı bastırarak, Efektinizin bağlı olduğu değerler hakkında React’e “yalan söylemiş” olursunuz.

Bunun yerine aşağıdaki teknikleri kullanın.

Derinlemesine İnceleme

Bağımlılık linterini bastırmak neden bu kadar tehlikeli?

Linteri bastırmak, bulunması ve düzeltilmesi zor olan çok mantıksız hatalara yol açar. İşte bir örnek:

import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  function onTick() {
	setCount(count + increment);
  }

  useEffect(() => {
    const id = setInterval(onTick, 1000);
    return () => clearInterval(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <h1>
        Sayaç: {count}
        <button onClick={() => setCount(0)}>Sıfırla</button>
      </h1>
      <hr />
      <p>
        Her saniye şu kadar artıyor:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}></button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}

Diyelim ki Efekti “sadece montajda” çalıştırmak istediniz. Boş ([]) bağımlılıkların bunu yaptığını okudunuz, bu yüzden linter’ı görmezden gelmeye karar verdiniz ve bağımlılıklar olarak zorla [] belirttiniz.

Bu sayacın her saniye iki düğme ile yapılandırılabilen miktar kadar artması gerekiyordu. Ancak, React’e bu Efektin hiçbir şeye bağlı olmadığı konusunda “yalan söylediğiniz” için, React ilk render’dan itibaren onTick fonksiyonunu sonsuza kadar kullanmaya devam ediyor. Bu render sırasında, count = 0 ve increment = 1 idi. Bu nedenle bu render’daki onTick her zaman her saniye setCount(0 + 1) çağırır ve her zaman 1 görürsünüz. Bunun gibi hatalar birden fazla bileşene yayıldığında düzeltilmesi daha zordur.

Her zaman linter’ı görmezden gelmekten daha iyi bir çözüm vardır! Bu kodu düzeltmek için bağımlılık listesine onTick eklemeniz gerekir. (Aralığın yalnızca bir kez ayarlandığından emin olmak için, onTicki bir Efekt Olayı yapın.)

Bağımlılık lint hatasını bir derleme hatası olarak ele almanızı öneririz. Bunu bastırmazsanız, bu gibi hataları asla görmezsiniz. Bu sayfanın geri kalanı, bu ve diğer durumlar için alternatifleri belgelemektedir.

Gereksiz bağımlılıkları kaldırma

Efektin bağımlılıklarını kodu yansıtacak şekilde her ayarladığınızda, bağımlılık listesine bakın. Bu bağımlılıklardan herhangi biri değiştiğinde Efektin yeniden çalıştırılması mantıklı mı? Bazen cevap “hayır” olabilir:

  • Efektinizin farklı bölümlerini farklı koşullar altında yeniden yürütmek isteyebilirsiniz.
  • Değişikliklere “tepki vermek” yerine bazı bağımlılıkların yalnızca en son değerini okumak isteyebilirsiniz.
  • Bir bağımlılık, bir nesne ya da fonksiyon olduğu için kasıtsız olarak çok sık değişebilir.

Doğru çözümü bulmak için, Efektiniz hakkında birkaç soruyu yanıtlamanız gerekir. Hadi bunların üzerinden geçelim.

Bu kod bir olay yöneticisine taşınmalı mı?

Düşünmeniz gereken ilk şey, bu kodun bir efekt olup olmaması gerektiğidir.

Bir form düşünün. Gönderildiğinde, submitted state değişkenini true olarak ayarlarsınız. Bir POST isteği göndermeniz ve bir bildirim göstermeniz gerekir. Bu mantığı, submitted state’inin true olmasına “tepki veren” bir Efektin içine yerleştirirsiniz:

function Form() {
const [submitted, setSubmitted] = useState(false);

useEffect(() => {
if (submitted) {
// 🔴 Kaçının: Bir Efekt içinde olaya özgü mantık
post('/api/register');
showNotification('Başarıyla Kaydedildi!');
}
}, [submitted]);

function handleSubmit() {
setSubmitted(true);
}

// ...
}

Daha sonra, bildirim mesajını mevcut temaya göre şekillendirmek istersiniz, bu nedenle mevcut temayı okursunuz. Bileşen gövdesinde theme bildirildiği için reaktif bir değerdir, bu nedenle onu bir bağımlılık olarak eklersiniz:

function Form() {
const [submitted, setSubmitted] = useState(false);
const theme = useContext(ThemeContext);

useEffect(() => {
if (submitted) {
// 🔴 Kaçının: Bir Efekt içinde olaya özgü mantık
post('/api/register');
showNotification('Başarıyla Kaydedildi!', theme);
}
}, [submitted, theme]); // ✅ Tüm bağımlılıklar bildirildi

function handleSubmit() {
setSubmitted(true);
}

// ...
}

Bunu yaparak bir hatayı ortaya çıkarmış olursunuz. Önce formu gönderdiğinizi ve ardından Koyu ve Açık temalar arasında geçiş yaptığınızı düşünün. Tema değişecek, Efekt yeniden çalışacak ve böylece aynı bildirimi tekrar görüntüleyecektir!

Buradaki sorun, bunun ilk etapta bir Efekt olmaması gerektiğidir. Bu POST isteğini göndermek ve belirli bir etkileşim olan formun gönderilmesine yanıt olarak bildirimi göstermek istiyorsunuz. Belirli bir etkileşime yanıt olarak bazı kodları çalıştırmak için, bu mantığı doğrudan ilgili olay yöneticisine yerleştirin:

function Form() {
const theme = useContext(ThemeContext);

function handleSubmit() {
// ✅ Güzel: Olaya özgü mantık olay yöneticilerinden çağrılır
post('/api/register');
showNotification('Başarıyla Kaydedildi!', theme);
}

// ...
}

Artık kod bir olay yöneticisinde olduğu için reaktif değildir—bu nedenle yalnızca kullanıcı formu gönderdiğinde çalışacaktır. Olay yöneticileri ve Efektler arasında seçim yapma ve gereksiz Etkiler nasıl silinir hakkında daha fazla bilgi edinin

Efektiniz birbiriyle alakasız birkaç şey mi yapıyor?

Kendinize sormanız gereken bir sonraki soru, Efektinizin birbiriyle alakasız birkaç şey yapıp yapmadığıdır.

Kullanıcının şehir ve bölgesini seçmesi gereken bir gönderi formu oluşturduğunuzu düşünün. Seçilen country’e göre cities listesini sunucudan alıp bir açılır menüde gösteriyorsunuz:

function ShippingForm({ country }) {
const [cities, setCities] = useState(null);
const [city, setCity] = useState(null);

useEffect(() => {
let ignore = false;
fetch(`/api/cities?country=${country}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setCities(json);
}
});
return () => {
ignore = true;
};
}, [country]); // ✅ Tüm bağımlılıklar bildirildi

// ...

Bu,bir Efekte veri getirmeye iyi bir örnektir. cities state’i country prop’una göre ağ ile senkronize ediyorsunuz. Bunu bir olay yöneticisinde yapamazsınız çünkü ShippingForm görüntülendiğinde ve country değiştiğinde (hangi etkileşim buna neden olursa olsun) getirmeniz gerekir.

Şimdi diyelim ki şehir alanları için ikinci bir seçim kutusu ekliyorsunuz, bu da o anda seçili olan city için areası getirmelidir. Aynı Efekt içindeki alanların listesi için ikinci bir fetch çağrısı ekleyerek başlayabilirsiniz:

function ShippingForm({ country }) {
const [cities, setCities] = useState(null);
const [city, setCity] = useState(null);
const [areas, setAreas] = useState(null);

useEffect(() => {
let ignore = false;
fetch(`/api/cities?country=${country}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setCities(json);
}
});
// 🔴 Kaçının: Tek bir Efekt iki bağımsız süreci senkronize eder
if (city) {
fetch(`/api/areas?city=${city}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setAreas(json);
}
});
}
return () => {
ignore = true;
};
}, [country, city]); // ✅ Tüm bağımlılıklar bildirildi

// ...

Ancak, Efekt artık city state değişkenini kullandığından, bağımlılıklar listesine city eklemek zorunda kaldınız. Bu da bir sorun ortaya çıkardı: Kullanıcı farklı bir şehir seçtiğinde, Efekt yeniden çalışacak ve fetchCities(country) öğesini çağıracaktır. Sonuç olarak, şehir listesini gereksiz yere birçok kez yeniden çağırmış olursunuz.

Bu koddaki sorun, iki farklı ilgisiz şeyi senkronize ediyor olmanızdır:

  1. cities state’ini country prop’una göre ağ ile senkronize etmek istiyorsunuz.
  2. areas state’ini city stateine göre ağ ile senkronize etmek istiyorsunuz.

Mantığı, her biri senkronize olması gereken prop’a tepki veren iki Efekt’e bölün:

function ShippingForm({ country }) {
const [cities, setCities] = useState(null);
useEffect(() => {
let ignore = false;
fetch(`/api/cities?country=${country}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setCities(json);
}
});
return () => {
ignore = true;
};
}, [country]); // ✅ Tüm bağımlılıklar bildirildi

const [city, setCity] = useState(null);
const [areas, setAreas] = useState(null);
useEffect(() => {
if (city) {
let ignore = false;
fetch(`/api/areas?city=${city}`)
.then(response => response.json())
.then(json => {
if (!ignore) {
setAreas(json);
}
});
return () => {
ignore = true;
};
}
}, [city]); // ✅ Tüm bağımlılıklar bildirildi

// ...

Şimdi ilk Efekt yalnızca country değiştiğinde yeniden çalışırken, ikinci Efekt city değiştiğinde yeniden çalışır. Bunları amaçlarına göre ayırdınız: iki farklı şey iki ayrı Efekt tarafından senkronize ediliyor. İki ayrı Efektin iki ayrı bağımlılık listesi vardır, bu nedenle istemeden birbirlerini tetiklemezler.

Son kod orijinalinden daha uzundur, ancak bu Efektleri bölmek hala doğrudur. Her Efekt bağımsız bir senkronizasyon sürecini temsil etmelidir Bu örnekte, bir Efektin silinmesi diğer Efektin mantığını bozmaz. Bu, farklı şeyleri senkronize ettikleri ve onları ayırmanın iyi olduğu anlamına gelir. Tekrarlama konusunda endişeleriniz varsa, bu kodu tekrarlayan mantığı özel bir hook çıkararak geliştirebilirsiniz.

Bir sonraki state’i hesaplamak için bir state mi okuyorsunuz?

Bu Efekt, her yeni mesaj geldiğinde messages state değişkenini yeni oluşturulan bir dizi ile günceller:

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages([...messages, receivedMessage]);
});
// ...

Mevcut tüm mesajlardan başlayarak yeni bir dizi oluşturmak için messages değişkenini kullanır ve sonuna yeni mesajı ekler. Ancak, messages bir Efekt tarafından okunan reaktif bir değer olduğundan, bir bağımlılık olmalıdır:

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages([...messages, receivedMessage]);
});
return () => connection.disconnect();
}, [roomId, messages]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Ve messages’ı bir bağımlılık haline getirmek bir sorun yaratır.

Her mesaj aldığınızda, setMessages() bileşenin alınan mesajı içeren yeni bir messages dizisiyle yeniden renderlanmasına neden olur. Ancak, bu Efekt artık messages dizisine bağlı olduğundan, bu aynı zamanda Efekti yeniden senkronize edecektir. Yani her yeni mesaj sohbetin yeniden bağlanmasını sağlayacaktır. Kullanıcı bundan hoşlanmayacaktır!

Sorunu çözmek için, messages’ı Efekt içinde okumayın. Bunun yerine, setMessages öğesine bir güncelleyici fonksiyon iletin:

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages(msgs => [...msgs, receivedMessage]);
});
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi.
// ...

Efekt’inizin artık messages değişkenini nasıl okumadığına dikkat edin. Sadece msgs => [...msgs, receivedMessage] gibi bir güncelleyici fonksiyonu geçirmeniz gerekir. React güncelleyici fonksiyonunuzu bir kuyruğa koyar ve bir sonraki render sırasında msgs argümanını ona sağlayacaktır. Bu nedenle Efektin kendisinin artık messages’a bağlı olması gerekmez. Bu düzeltmenin bir sonucu olarak, bir sohbet mesajı almak artık sohbetin yeniden bağlanmasına neden olmayacaktır.

Bir değeri, değişikliklerine “tepki vermeden” okumak mı istiyorsunuz?

Yapım Halinde

Bu bölümde, React’in kararlı bir sürümünde henüz yayınlanmamış deneysel bir API açıklanmaktadır.

Kullanıcı yeni bir mesaj aldığında isMuted değeri true olmadığı sürece bir ses çalmak istediğinizi varsayalım:

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [isMuted, setIsMuted] = useState(false);

useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages(msgs => [...msgs, receivedMessage]);
if (!isMuted) {
playSound();
}
});
// ...

Efektiniz artık kodunda isMuted kullandığından, bunu bağımlılıklara eklemeniz gerekir:

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [isMuted, setIsMuted] = useState(false);

useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
setMessages(msgs => [...msgs, receivedMessage]);
if (!isMuted) {
playSound();
}
});
return () => connection.disconnect();
}, [roomId, isMuted]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Sorun şu ki, isMuted her değiştiğinde (örneğin, kullanıcı “Muted” düğmesine bastığında), Efekt yeniden senkronize olacak ve sohbete yeniden bağlanacaktır. Bu istenen kullanıcı deneyimi değildir! (Bu örnekte, linter’ı devre dışı bırakmak bile işe yaramayacaktır - eğer bunu yaparsanız, isMuted eski değerine “takılıp kalacaktır”).

Bu sorunu çözmek için, reaktif olmaması gereken mantığı Efektin dışına çıkarmanız gerekir. Bu Efektin isMuted içindeki değişikliklere “tepki vermesini” istemezsiniz. Bu reaktif olmayan mantık parçasını bir Efekt Olayına taşıyın:

import { useState, useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [isMuted, setIsMuted] = useState(false);

const onMessage = useEffectEvent(receivedMessage => {
setMessages(msgs => [...msgs, receivedMessage]);
if (!isMuted) {
playSound();
}
});

useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Efekt Olayları, bir Efekti reaktif parçalara (roomId gibi reaktif değerlere ve bunların değişikliklerine “tepki” vermesi gereken) ve reaktif olmayan parçalara (onMessageın isMutedı okuması gibi yalnızca en son değerlerini okuyan) ayırmanıza olanak tanır. Artık isMuted değerini bir Efekt Olayı içinde okuduğunuz için, Efektinizin bir bağımlılığı olması gerekmez. Sonuç olarak, “Muted” ayarını açıp kapattığınızda sohbet yeniden bağlanmayacak ve orijinal sorunu çözecektir!

Bir olay yöneticisini prop’lardan sarma

Bileşeniniz prop olarak bir olay yöneticisi aldığında da benzer bir sorunla karşılaşabilirsiniz:

function ChatRoom({ roomId, onReceiveMessage }) {
const [messages, setMessages] = useState([]);

useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onReceiveMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId, onReceiveMessage]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Ana bileşenin her render işleminde farklı bir onReceiveMessage fonksiyonu geçirdiğini varsayalım:

<ChatRoom
roomId={roomId}
onReceiveMessage={receivedMessage => {
// ...
}}
/>

onReceiveMessage bir bağımlılık olduğundan, her üst yeniden renderdan sonra Efektin yeniden senkronize olmasına neden olur. Bu da sohbete yeniden bağlanmasına neden olur. Bunu çözmek için, çağrıyı bir Efekt Olayına sarın:

function ChatRoom({ roomId, onReceiveMessage }) {
const [messages, setMessages] = useState([]);

const onMessage = useEffectEvent(receivedMessage => {
onReceiveMessage(receivedMessage);
});

useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
onMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Efekt Olayları reaktif değildir, bu nedenle bunları bağımlılık olarak belirtmeniz gerekmez. Sonuç olarak, ana bileşen her yeniden renderda farklı bir fonksiyon geçirse bile sohbet artık yeniden bağlanmayacaktır.

Reaktif ve reaktif olmayan kodu ayırma

Bu örnekte, roomId her değiştiğinde bir ziyareti günlüğe kaydetmek istiyorsunuz. Her günlüğe geçerli notificationCount değerini dahil etmek istiyorsunuz, ancak notificationCount değerindeki bir değişikliğin bir günlük olayını tetiklemesini istemiyorsunuz.

Çözüm yine reaktif olmayan kodu bir Efekt Olayına ayırmaktır:

function Chat({ roomId, notificationCount }) {
const onVisit = useEffectEvent(visitedRoomId => {
logVisit(visitedRoomId, notificationCount);
});

useEffect(() => {
onVisit(roomId);
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...
}

Mantığınızın roomId ile ilgili olarak reaktif olmasını istiyorsunuz, bu nedenle Efektinizin içinde roomId değerini okuyorsunuz. Ancak, notificationCount değerinde yapılan bir değişikliğin fazladan bir ziyareti günlüğe kaydetmesini istemezsiniz, bu nedenle notificationCount değerini Efekt Olayının içinde okursunuz. Efekt Olaylarını kullanarak Efektlerden en son props ve state’leri okuma hakkında daha fazla bilgi edinin

Bazı reaktif değerler istemeden değişiyor mu?

Bazen, Efektinizin belirli bir değere “tepki vermesini” istersiniz, ancak bu değer istediğinizden daha sık değişir ve kullanıcının bakış açısından herhangi bir gerçek değişikliği yansıtmayabilir. Örneğin, bileşeninizin gövdesinde bir options nesnesi oluşturduğunuzu ve daha sonra bu nesneyi Efektinizin içinden okuduğunuzu varsayalım:

function ChatRoom({ roomId }) {
// ...
const options = {
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ...

Bu nesne bileşen gövdesinde bildirilir, bu nedenle bir reaktif değerdir. Bunun gibi bir reaktif değeri bir Efekt içinde okuduğunuzda, onu bir bağımlılık olarak bildirirsiniz. Bu, Efektinizin onun değişikliklerine “tepki vermesini” sağlar:

// ...
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Bunu bir bağımlılık olarak bildirmek önemlidir! Bu, örneğin roomId değişirse, Efektinizin yeni seçenekler ile sohbete yeniden bağlanmasını sağlar. Ancak, yukarıdaki kodda da bir sorun var. Bunu görmek için, aşağıdaki sandbox’taki girdiye yazmayı deneyin ve konsolda ne olduğunu izleyin:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  // Sorunu göstermek için linteri geçici olarak devre dışı bırakın
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]);

  return (
    <>
      <h1>{roomId} odasına Hoş Geldiniz</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyehat">Seyehat</option>
          <option value="müzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Yukarıdaki sanal alanda, girdi yalnızca message state değişkenini günceller. Kullanıcının bakış açısından, bunun sohbet bağlantısını etkilememesi gerekir. Ancak, message değişkenini her güncellediğinizde, bileşeniniz yeniden renderlanır. Bileşeniniz yeniden renederlandığında, içindeki kod sıfırdan yeniden çalışır.

ChatRoom bileşeninin her yeniden renderlanmasında sıfırdan yeni bir options nesnesi oluşturulur. React, options nesnesinin son render sırasında oluşturulan options nesnesinden farklı bir nesne olduğunu görür. Bu nedenle Efektinizi yeniden senkronize eder (ki bu optionse bağlıdır) ve sohbet siz yazarken yeniden bağlanır.

Bu sorun yalnızca nesneleri ve fonksiyonları etkiler. JavaScript’te, yeni oluşturulan her nesne ve fonksiyon diğerlerinden farklı kabul edilir. İçlerindeki içeriklerin aynı olması önemli değildir!

// İlk rener sırasında
const options1 = { serverUrl: 'https://localhost:1234', roomId: 'müzik' };

// Sonraki render sırasında
const options2 = { serverUrl: 'https://localhost:1234', roomId: 'müzik' };

// Bunlar iki farklı nesne!
console.log(Object.is(options1, options2)); // false

Nesne ve fonksiyon bağımlılıkları, Efektinizin ihtiyacınız olandan daha sık yeniden senkronize edilmesine neden olabilir.

Bu nedenle, mümkün olduğunca, Efektinizin bağımlılıkları olarak nesnelerden ve fonksiyonlardan kaçınmaya çalışmalısınız. Bunun yerine, bunları bileşenin dışına, Efektin içine taşımayı veya ilkel değerleri bunlardan çıkarmayı deneyin.

Statik nesneleri ve fonksiyonları bileşeninizin dışına taşıma

Nesne herhangi bir prop ve state’e bağlı değilse, bu nesneyi bileşeninizin dışına taşıyabilirsiniz:

const options = {
serverUrl: 'https://localhost:1234',
roomId: 'music'
};

function ChatRoom() {
const [message, setMessage] = useState('');

useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Tüm bağımlılıklar bildirildi
// ...

Bu şekilde, linter’a reaktif olmadığını kanıtlamış olursunuz. Yeniden renderlamanın bir sonucu olarak değişemez, bu nedenle bir bağımlılık olması gerekmez. Şimdi ChatRoomun yeniden renderlaması Efektinizin yeniden senkronize edilmesine neden olmaz.

Bu fonksiyonlar için de geçerlidir:

function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: 'music'
};
}

function ChatRoom() {
const [message, setMessage] = useState('');

useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Tüm bağımlılıklar bildirildi
// ...

createOptions bileşeninizin dışında bildirildiği için reaktif bir değer değildir. Bu nedenle Efektinizin bağımlılıklarında belirtilmesi gerekmez ve bu nedenle Efektinizin yeniden senkronize olmasına neden olmaz.

Dinamik nesneleri ve fonksiyonları Efektinizin içine taşıma

Nesneniz yeniden renderlaması sonucunda değişebilecek bir reaktif değere bağlıysa, örneğin bir roomId prop’u gibi, onu bileşeninizin dışına çekemezsiniz. Bununla birlikte, oluşturulmasını Efekt kodunuzun içine taşıyabilirsiniz:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Artık options Efektinizin içinde bildirildiği için, Efektinizin bir bağımlılığı değildir. Bunun yerine, Efektiniz tarafından kullanılan tek reaktif değer roomIddir. roomId` bir nesne ya da fonksiyon olmadığından, kasıtsız olarak farklı olmayacağından emin olabilirsiniz. JavaScript’te sayılar ve dizeler içeriklerine göre karşılaştırılır:

// İlk render sırasında
const roomId1 = 'müzik';

// Sonraki render sırasında
const roomId2 = 'müzik';

// Bu iki string de aynı!
console.log(Object.is(roomId1, roomId2)); // true

Bu düzeltme sayesinde, girişi düzenlediğinizde sohbet artık yeniden bağlanmıyor:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>{roomId} odasına hoş geldiniz</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">Genel</option>
          <option value="seyehat">Seyehat</option>
          <option value="müzik">Müzik</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Ancak, roomId açılır menüsünü değiştirdiğinizde, beklediğiniz gibi yeniden bağlanır.

Bu, fonksiyonlar için de geçerlidir:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
function createOptions() {
return {
serverUrl: serverUrl,
roomId: roomId
};
}

const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Efektinizin içindeki mantık parçalarını gruplamak için kendi fonksiyonlarınızı yazabilirsiniz. Bunları Efektinizin içinde de bildirdiğiniz sürece, reaktif değerler değildirler ve bu nedenle Efektinizin bağımlılıkları olmaları gerekmez.

Nesnelerden ilkel değerleri okuma

Bazen proplardan bir nesne alabilirsiniz:

function ChatRoom({ options }) {
const [message, setMessage] = useState('');

useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Buradaki risk, ana bileşenin renderlanması sırasında nesneyi renderlamasıdır:

<ChatRoom
roomId={roomId}
options={{
serverUrl: serverUrl,
roomId: roomId
}}
/>

Bu, ana bileşen her yeniden renderlandığında Efektinizin yeniden bağlanmasına neden olur. Bunu düzeltmek için, Efektin dışındaki nesneden bilgi okuyun ve nesne ve fonksiyon bağımlılıklarına sahip olmaktan kaçının:

function ChatRoom({ options }) {
const [message, setMessage] = useState('');

const { roomId, serverUrl } = options;
useEffect(() => {
const connection = createConnection({
roomId: roomId,
serverUrl: serverUrl
});
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Mantık biraz tekrara düşüyor (bir Efektin dışındaki bir nesneden bazı değerleri okuyorsunuz ve ardından Efektin içinde aynı değerlere sahip bir nesne oluşturuyorsunuz). Ancak bu, Efektinizin gerçekte hangi bilgilere bağlı olduğunu çok açık hale getirir. Bir nesne ana bileşen tarafından istenmeden yeniden oluşturulursa, sohbet yeniden bağlanmaz. Ancak, options.roomId veya options.serverUrl gerçekten farklıysa, sohbet yeniden bağlanır.

Fonksiyonlardan ilkel değerleri hesaplama

Aynı yaklaşım fonksiyonlar için de kullanılabilir. Örneğin, ana bileşenin bir fonksiyon geçirdiğini varsayalım:

<ChatRoom
roomId={roomId}
getOptions={() => {
return {
serverUrl: serverUrl,
roomId: roomId
};
}}
/>

Bunu bir bağımlılık haline getirmekten (ve yeniden renderlamalarda yeniden bağlanmasına neden olmaktan) kaçınmak için, bunu Efektin dışında çağırın. Bu size nesne olmayan ve Efektinizin içinden okuyabileceğiniz roomId ve serverUrl değerlerini verir:

function ChatRoom({ getOptions }) {
const [message, setMessage] = useState('');

const { roomId, serverUrl } = getOptions();
useEffect(() => {
const connection = createConnection({
roomId: roomId,
serverUrl: serverUrl
});
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Bu sadecesaf halde fonksiyonlar için geçerlidir, çünkü render sırasında çağrılmaları güvenlidir. Fonksiyonunuz bir olay yöneticisiyse, ancak değişikliklerinin Efektinizi yeniden senkronize etmesini istemiyorsanız,bunun yerine bir Efekt Olayına sarın.

Özet

  • Bağımlılıklar her zaman kodla eşleşmelidir.
  • Bağımlılıklarınızdan memnun olmadığınızda, düzenlemeniz gereken şey koddur.
  • Linteri bastırmak çok kafa karıştırıcı hatalara yol açar ve bundan her zaman kaçınmalısınız.
  • Bir bağımlılığı kaldırmak için, linter’e bunun gerekli olmadığını “kanıtlamanız” gerekir.
  • Bazı kodların belirli bir etkileşime yanıt olarak çalışması gerekiyorsa, bu kodu bir olay yöneticisine taşıyın.
  • Efektinizin farklı bölümlerinin farklı nedenlerle yeniden çalıştırılması gerekiyorsa, onu birkaç Efekte bölün.
  • Bir önceki durumu temel alarak bazı durumları güncellemek istiyorsanız, bir güncelleyici fonksiyonu geçirin.
  • En son değeri “tepki vermeden” okumak istiyorsanız, Efektinizden bir Efekt Olayı çıkarın.
  • JavaScript’te, nesneler ve fonksiyonlar farklı zamanlarda oluşturulmuşlarsa farklı kabul edilirler.
  • Nesne ve fonksiyon bağımlılıklarından kaçınmaya çalışın. Bunları bileşenin dışına veya Efektin içine taşıyın.

Problem 1 / 4:
Sıfırlama aralığını düzeltme

Bu Efekt, her saniyede bir işleyen bir aralık oluşturur. Tuhaf bir şeyin olduğunu fark ettiniz: Sanki aralık her tıklandığında yok ediliyor ve yeniden yaratılıyor gibi görünüyor. Kodu, aralığın sürekli olarak yeniden oluşturulmayacağı şekilde düzeltin.

import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('✅ Bir aralık oluşturma');
    const id = setInterval(() => {
      console.log('⏰ Aralık işareti');
      setCount(count + 1);
    }, 1000);
    return () => {
      console.log('❌ Bir aralığı temizleme');
      clearInterval(id);
    };
  }, [count]);

  return <h1>Sayaç: {count}</h1>
}