useEffect
useEffect
, bir bileşeni harici bir sistem ile senkronize etmenizi sağlayan React Hook’udur.
useEffect(setup, dependencies?)
- Referans
- Kullanım
- Harici bir sisteme bağlanma
- Effect’leri özel Hook’larla sarma
- React olmayan widget’ı kontrol etme
- Effect’ler ile veri getirme (fetching)
- Reaktif bağımlılıkları belirleme
- Effect’ten önceki state’e göre state’i güncelleme
- Gereksiz nesne bağımlılıklarını kaldırma
- Gereksiz fonksiyon bağımlılıklarını kaldırma
- Effect’te nihai prop’ları ve state’i okuma
- Sunucu ve kullanıcıda farklı içerikler gösterme
- Sorun giderme
Referans
useEffect(setup, dependencies?)
Bir Effect bildirmek için bileşeninizin en üst düzeyinde useEffect
’i çağırın:
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
Daha fazla örnek görmek için aşağıya bakınız.
Parametreler
-
setup
: Effect’inizin mantığını içeren fonksiyon. Kurulum (setup) fonksiyonunuz isteğe bağlı olarak temizleme (cleanup) fonksiyonu da döndürebilir. Bileşeniniz DOM’a eklendiğinde, React kurulum fonksiyonunuzu çalıştıracaktır. Değişen bağımlılıklar ile her yeniden render işleminden sonra, React önce temizleme fonksiyonunu (eğer sağladıysanız) eski değerlerle çalıştıracak ve ardından kurulum fonksiyonunuzu yeni değerlerle çalıştıracaktır. Bileşeniniz DOM’dan kaldırıldıktan sonra, React temizleme fonksiyonunuzu çalıştıracaktır. -
Opsiyonel
bağımlılıklar
:kurulum
(setup
) kodunun içinde referansı olan tüm reaktif değerlerin listesi. Reaktif değerler prop’ları, state’i ve bileşeninizin gövdesi içinde bildirilen tüm değişkenleri ve fonksiyonları içerir. Linter’ınız React için yapılandırılmış ise, her reaktif değerin bağımlılık olarak doğru bir şekilde belirtildiğini doğrulayacaktır. Bağımlılık listesi sabit sayıda öğeye sahip olmalı ve[dep1, dep2, dep3]
şeklinde satır içinde yazılmalıdır. React,Object.is
karşılaştırmasını kullanarak her bağımlılığı önceki değeri ile karşılaştırır. Eğer bağımlılık listesini boş bırakırsanız, Effect’iniz her yeniden render’dan sonra tekrar çalışacaktır. Bağımlılık dizisi iletmenin, boş dizi iletmenin ve hiç bağımlılık olmaması arasındaki farkı inceleyin.
Dönüş Değeri
useEffect
, undefined
döndürür.
Uyarılar
-
useEffect
bir Hook’tur, dolayısıyla bu Hook’u yalnızca bileşeninizin en üst seviyesinde veya kendi Hook’larınızda çağırabilirsiniz. Döngüler veya koşullu ifadeler içinde çağıramazsınız. Eğer çağırmak istiyorsanız, yeni bir bileşen oluşturun ve state’i onun içine taşıyın. -
Eğer harici sistemle senkronize etmeye çalışmıyorsanız, büyük ihtimalle Effect’e ihtiyacınız yoktur.
-
Strict Modu kullanırken, React ilk gerçek kurulumdan önce sadece geliştirme sırasında olmak üzere ekstra bir kurulum+temizleme döngüsü çalıştırır. Bu, temizleme mantığınızın kurulum mantığınızı “yansıtmasını” ve kurulumun yaptığı her şeyi durdurmasını ya da geri almasını sağlayan bir stres testidir. Eğer bu bir sorun yaratıyorsa, temizleme fonksiyonunu uygulayın.
-
Eğer bağımlılıklarınızdan bazıları nesneler veya bileşeniniz içinde tanımlanmış fonksiyonlar ise, bu bağımlılıkların Effect’in gerekenden daha sık yeniden çalışmasına neden olma riski vardır. Bu durumu düzeltmek için, gereksiz nesne ve fonksiyon bağımlılıklarını silin. Ayrıca state güncellemelerinizi ve reaktif olmayan mantığı Effect dışına taşıyabilirsiniz.
-
Eğer Effect’inizin çalışmasına bir etkileşim (tıklama gibi) neden oluyorsa, React genellikle, Effect’inizi çalıştırmadan önce tarayıcının güncellenen ekranı çizmesine izin verecektir. Eğer Effect’iniz görsel (örneğin ipucu gösterme) bir şey yapıyorsa ve gecikme gözle görülebilir gibiyse (örneğin titriyorsa),
useEffect
’iuseLayoutEffect
ile değiştirin. -
Effect’inizin çalışmasına bir etkileşim (tıklama gibi) neden oluyor olsa bile, tarayıcı Effect’iniz içindeki state güncellemelerini işlemeden önce ekranı yeniden çizebilir. Genellikle, istediğiniz şey budur. Ancak, tarayıcının ekranı yeniden çizmesini engellemek zorundaysanız,
useEffect
’iuseLayoutEffect
ile değiştirmelisiniz. -
Effect’ler sadece kullanıcı (client) tarafında çalışır. Sunucu render etme sırasında çalışmazlar.
Kullanım
Harici bir sisteme bağlanma
Bazı bileşenlerin sayfada görüntülenebilmesi için ağa, bazı tarayıcı API’larına ya da üçüncü parti kütüphanelere bağlı kalması gerekir. Bu sistemler React tarafından kontrol edilmezler, bu yüzden harici olarak adlandırılırlar.
Bileşeninizi harici bir sisteme bağlamak için, bileşeninizin en üst düzeyinde useEffect
’i çağırın:
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
useEffect
’e iki argüman iletmeniz gerekmektedir:
- Bu sisteme bağlanan kurulum (setup) kodu içeren bir kurulum fonksiyonu.
- Bu sistemle olan bağlantıyı kesen temizleme (cleanup) kodu içeren bir temizleme fonksiyonu döndürmeli.
- Bileşeninizden bu fonksiyonların içinde kullanılan her bir değeri içeren bağımlılıklar listesi.
React, kurulum ve temizleme fonksiyonlarınızı gerektiğinde birden çok kez olabilecek şekilde çağırır:
- Kurulum kodunuz bileşeniniz sayfaya eklendiğinde çalışır (DOM’a eklendiğinde).
- Bileşeninizin bağımlılıklarının değiştiği her yeniden render etmeden sonra:
- İlk olarak, temizleme kodunuz eski prop’lar ve state ile çalışır.
- Daha sonra, kurulum kodunuz yeni prop’lar ve state ile çalışır.
- temizleme kodunuz son kez bileşeniniz sayfadan kaldırıldığında çalışır (DOM’dan kaldırıldığında).
Yukarıdaki örneği biraz açıklayalım.
Yukarıdaki ChatRoom
bileşeni sayfaya eklendiğinde, başlangıç serverUrl
ve roomId
ile sohbet odasına bağlanacaktır. Eğer serverUrl
veya roomId
’den biri yeniden render yüzünden değişirse (diyelim ki kullanıcı başka bir sohbet odasını seçerse), Effect’iniz önceki odayla bağlantısını kesecek ve bir sonraki odaya bağlanacaktır. ChatRoom
bileşeniniz sayfadan kaldırıldığında, Effect’iniz son bir defa bağlantıyı kesecektir.
Geliştirme sırasında hataları bulmanıza yardımcı olmak için React, kurulum ve temizleme kodunu kurulum’dan önce son kez çalıştırır. Bu, Effect mantığınızın doğru uygulandığını doğrulayan bir stres testidir. Bu, gözle görünür sorunlara neden oluyorsa, temizleme fonksiyonunuzda bazı mantık hataları vardır. Temizleme fonksiyonu, kurulum fonksiyonunun yaptığı her şeyi durdurmalı ya da geri almalıdır. Temel kural, kullanıcı bir kez çağrılan kurulum (son üründe olduğu gibi) ile kurulum → temizleme → kurulum sekansı (geliştirme sırasında olduğu gibi) arasındaki farkı ayırt etmemelidir. Sık kullanılan çözümlere göz gezdirin.
Her Effect’i bağımsız bir süreç olarak yazmayı ve her seferinde tek kurulum/temizleme döngüsü düşünmeyi deneyin. Bileşeninizin DOM’a ekleniyor/çıkarılıyor ya da güncelleniyor olması fark etmemelidir. Temizleme mantığınız kurulum mantığını doğru bir şekilde “yansıttığında”, Effect’iniz kurulum ve temizlemeyi gerektiği sıklıkta çalıştıracaktır.
Örnek 1 / 5: Sohbet sunucusuna bağlanma
Bu örnekte, ChatRoom
bileşeni chat.js
’de bildirilen harici sisteme bağlı kalmak için Effect’i kullanmaktadır. “Sohbeti aç” butonuna tıklayarak ChatRoom
bileşenini render edin. Bu sandbox geliştirme modunda çalışmaktadır, bu yüzden fazladan bir bağlan ve bağlantıyı kes döngüsü burada açıklandığı gibi vardır. roomId
ve serverUrl
’yi aşağı doğru açılan menüyü (dropdown) ve input’u kullanarak değiştirin ve Effect’in nasıl tekrardan sohbete bağlandığını görün. “Sohbeti kapat” butonuna tıklayarak Effect’in son kez bağlantıyı kesmesini görün.
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [roomId, serverUrl]); return ( <> <label> Sunucu URL'i:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>{roomId} odasına hoş geldiniz!</h1> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); const [show, setShow] = useState(false); return ( <> <label> Sohbet odasını seçin:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="genel">Genel</option> <option value="seyahat">Seyahat</option> <option value="muzik">Müzik</option> </select> </label> <button onClick={() => setShow(!show)}> {show ? 'Sohbeti kapat' : 'Sohbeti aç'} </button> {show && <hr />} {show && <ChatRoom roomId={roomId} />} </> ); }
Effect’leri özel Hook’larla sarma
Effect’ler “kaçış kapaklarıdır”: Effect’leri “React’in dışına çıkmanız” gerektiğinde ve kullanım durumunuz için daha iyi yerleşik bir çözüm olmadığunda kullanırsınız. Kendinizi Effect’leri sık sık manuel olarak yazma durumunda buluyorsanız, bu genellikle bileşenlerinizin dayandığı yaygın davranışlar için özel Hook’lar yazmanız gerektiği anlamına gelir.
Örneğin, bu useChatRoom
özel Hook’u, Effect’inizin mantığını daha bildirimsel (declarative) bir API’ın arkasına “gizler”:
function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
Yazdığınız bu Hook’u herhangi başka bir bileşenden de şöyle kullanabilirsiniz:
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...
Ayrıca React ekosisteminde her amaca uygun çok sayıda mükemmel özel Hook’lar mevcuttur.
Effect’leri özel Hook’larla sarma konusunda daha fazla bilgi edinin.
Örnek 1 / 3: Özel useChatRoom
Hook’u
Bu örnek daha önceki örneklerden biriyle benzerdir ancak mantık özel bir Hook’a yazılmıştır.
import { useState } from 'react'; import { useChatRoom } from './useChatRoom.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useChatRoom({ roomId: roomId, serverUrl: serverUrl }); return ( <> <label> Sunucu URL'i:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>{roomId} odasına hoş geldiniz!</h1> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); const [show, setShow] = useState(false); return ( <> <label> Sohbet odasını seçin:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="genel">Genel</option> <option value="seyahat">Seyahat</option> <option value="muzik">Müzik</option> </select> </label> <button onClick={() => setShow(!show)}> {show ? 'Sohbeti kapat' : 'Sohbeti aç'} </button> {show && <hr />} {show && <ChatRoom roomId={roomId} />} </> ); }
React olmayan widget’ı kontrol etme
Bazen, harici bir sistemi bileşeninizin bazı prop’larına ya da state’ine göre senkronize etmek istersiniz.
Örneğin, React olmadan yazılmış bir üçünü parti harita widget’ınız veya bir video oynatıcı bileşeniniz varsa, o bileşenin state’ini React bileşeninizin şu anki state’iyle eşleştiren metodları çağırmak için Effect’i kullanabilirsiniz. Bu Effect, map-widget.js
içinde tanımlanan bir MapWidget
sınıfı örneği oluşturur. Map
bileşeninin zoomLevel
prop’unu değiştirdiğizde, Effect sınıf örneğini senkronize tutmak için setZoom()
fonksiyonunu çağırır:
import { useRef, useEffect } from 'react'; import { MapWidget } from './map-widget.js'; export default function Map({ zoomLevel }) { const containerRef = useRef(null); const mapRef = useRef(null); useEffect(() => { if (mapRef.current === null) { mapRef.current = new MapWidget(containerRef.current); } const map = mapRef.current; map.setZoom(zoomLevel); }, [zoomLevel]); return ( <div style={{ width: 200, height: 200 }} ref={containerRef} /> ); }
Bu örnekte, MapWidget
sınıfı yalnızca kendisine iletilen DOM node’unu yönettiği için bir temizleme fonksiyonu gerekli değildir. Map
React bileşeni ağaçtan kaldırıldıktan sonra, hem DOM node’u hem de MapWidget
sınıf örneği, tarayıcı JavaScript motoru tarafından otomatik olarak temizlenecektir.
Effect’ler ile veri getirme (fetching)
Bileşeninize veri getirmek için Effect’i kullanabilirsiniz. Eğer bir çatı kullanıyorsanız, çatının veri getirme mekanizmasını kullanmanın Effect’i manuel olarak yazmaktan çok daha verimli olacağını unutmayın.
Eğer manuel olarak Effect ile veri getirmek istiyorsanız, kodunuz şöyle görünebilir:
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);
// ...
Başlangıçta false
olan ve temizleme sırasında true
olan ignore
değişkenine dikkat edin. Bu, kodunuzun “yarış koşullarından” zarar görmemesini sağlar: ağdan gelen yanıtlar sizin onları gönderdiğiniz sıradan farklı olabilir.
import { useState, useEffect } from 'react'; import { fetchBio } from './api.js'; export default function Page() { const [person, setPerson] = useState('Alice'); const [bio, setBio] = useState(null); useEffect(() => { let ignore = false; setBio(null); fetchBio(person).then(result => { if (!ignore) { setBio(result); } }); return () => { ignore = true; } }, [person]); return ( <> <select value={person} onChange={e => { setPerson(e.target.value); }}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> <option value="Taylor">Taylor</option> </select> <hr /> <p><i>{bio ?? 'Yükleniyor...'}</i></p> </> ); }
async
/ await
sözdizimini kullanarak da yeniden yazabilirsiniz, ancak yine de bir temizleme fonksiyonu sağlamanız gerekmektedir:
import { useState, useEffect } from 'react'; import { fetchBio } from './api.js'; export default function Page() { const [person, setPerson] = useState('Alice'); const [bio, setBio] = useState(null); useEffect(() => { async function startFetching() { setBio(null); const result = await fetchBio(person); if (!ignore) { setBio(result); } } let ignore = false; startFetching(); return () => { ignore = true; } }, [person]); return ( <> <select value={person} onChange={e => { setPerson(e.target.value); }}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> <option value="Taylor">Taylor</option> </select> <hr /> <p><i>{bio ?? 'Yükleniyor...'}</i></p> </> ); }
Direkt olarak Effect ile veri getirmek tekrarlı hale gelir ve önbelleğe alma ve sunucudan render etme gibi optimizasyonların eklenmesini zorlaştırır. Kendiniz veya topluluk tarafından sağlanan özel bir Hook kullanmak daha kolaydır.
Derinlemesine İnceleme
Effect’ler içinde fetch
çağrıları yapmak, özellikle tamamen kullanıcı taraflı uygulamalarda veri getirmenin popüler bir yoludur. Ancak bu, çok manuel bir yaklaşımdır ve önemli dezavantajları vardır:
- Effect’ler sunucuda çalışmazlar. Bu, sunucu tarafından render edilen ilk HTML’in veri içermeyen bir yükleme state’ini içereceği anlamına gelir. Kullanıcı bilgisayarının tüm bu JavaScript’i indirmesi ve uygulamanızın şimdi verileri yüklemesi gerektiğini keşfetmesi için render etmesi gerekecektir. Bu çok verimli bir yol değildir.
- Doğrudan Effect ile veri getirmek, “ağ şelaleleri (waterfalls) oluşturmayı kolaylaştırır.” Üst bileşeni render edersiniz, o bileşen veri getirir, alt bileşenleri render eder, daha sonra o bileşenler kendi verilerini getirmeye başlarlar. Eğer internet bağlantınız hızlı değilse, verileri paralel olarak getirmeye göre önemli derecede yavaştır.
- Doğrudan Effect ile veri getirme, genellikle verileri önceden yüklememeniz veya önbelleğe almamanız anlamına gelir. Örneğin, bileşen DOM’dan kaldırılır ve sonra tekrar DOM’a eklenirse, bileşen aynı veriyi tekrar getirmek zorundadır.
- Ergonomik değildir. Yarış koşulları gibi hatalardan zarar görmeyecek şekilde
fetch
çağrıları yaparken oldukça fazla genel hatlarıyla kod yazmanız gerekmektedir.
Bu dezavantajlar listesi React’e özel değildir. Bu, herhangi bir kütüphane ile DOM’a eklenme sırasında yapılan veri getirme için geçerlidir. Yönlendirme (routing) de olduğu gibi, veri getirmenin iyi yapılması önemsiz değildir. Bu nedenle aşağıdaki yaklaşımları önermekteyiz:
- Eğer bir çatı kullanırsanız, çatının yerleşik veri getirme mekanizmasını kullanın. Modern React çatıları verimli veri getirme mekanizmalarını entegre etmişlerdir ve yukarıdaki tehlikelerden uzak dururlar.
- Aksi halde, kullanıcı taraflı bir önbellek çözümü kullanmayı ya da kendiniz oluşturmayı düşünün. Popüler açık kaynak çözümleri arasında React Query, useSWR ve React Router 6.4+ vardır. Kendi çözümlerinizi de oluşturabilirsiniz. Kendi çözümünüzü uygularsanız, arka planda Effect’leri kullanır ancak aynı zamanda istekleri tekilleştirmek, yanıtları önbelleğe almak ve ağ şelalelerinden kaçınmak (verileri önceden yükleyerek veya veri gereksinimlerini rotalara kaldırarak) gibi mantıkları da ekleyebilirsiniz.
Eğer bu yaklaşımlardan hiçbiri size uymuyorsa, Effect’ler içinde veri getirmeye devam edebilirsiniz.
Reaktif bağımlılıkları belirleme
Effect’inizin bağımlılıklarını “seçemeyeceğinize” dikkat edin. Effect’iniz tarafından kullanılan her reaktif değer bağımlılık olarak bildirilmelidir. Effect’inizin bağımlılık listesi çevreleyen kod tarafından belirlenir:
function ChatRoom({ roomId }) { // Bu reaktif bir değerdir
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Bu da reaktif bir değerdir
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Effect bu reaktif değerleri okur
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ Bu yüzden Effect'inizin bağımlılık listesinde belirtmeniz gerekmektedir
// ...
}
serverUrl
veya roomId
’den herhangi biri değişirse, Effect’iniz yeni değerleri kullanarak sohbete yeniden bağlanacaktır.
Reaktif değerler, prop’ları ve doğrudan bileşeniniz içinde bildirilen tüm değişkenleri ve fonksiyonları içerir. roomId
ve serverUrl
reaktif değerler olduğundan dolayı, bu değerleri bağımlılıktan kaldıramazsınız. Eğer kaldırmaya kalkarsanız ve linter’ınız React için ayarlanmışsa, linter bunu düzeltmeniz gereken bir hata olarak işaretleyecektir:
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook'u useEffect'te eksik bağımlılıklar var: 'roomId' and 'serverUrl'
// ...
}
Bağımlılığı kaldırmak için, linter’a bunun bir bağımlıklık olmasına gerek olmadığını “kanıtlamanız” gerekmektedir. Örneğin, reaktif olmadığını ve yeniden render’lar ile değişmeyeceğini kanıtlamak için serverUrl
’i bileşeninizin dışına taşıyabilirsiniz:
const serverUrl = 'https://localhost:1234'; // Artık reaktif bir değişken değil
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}
Artık serverUrl
reaktif bir değer olmadığına göre (ve yeniden render’lar ile değişmeyeceğine göre), bağımlılık olmasına gerek yoktur. Eğer Effect kodunuz herhangi bir reaktif değer kullanmıyorsa, bağımlılık listesi boş ([]
) olmalıdır:
const serverUrl = 'https://localhost:1234'; // Artık reaktif bir değer değil
const roomId = 'muzik'; // Artık reaktif bir değer değil
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ Bütün bağımlılıklar bildirilmiş
// ...
}
Boş bağımlılık listesi olan bir Effect herhangi bir bileşeninizin prop’ları ya da state’i değiştiğinde yeniden çalıştırılmaz.
Örnek 1 / 3: Bağımlılık dizisi iletme
Eğer bağımlılıkları belirtirseniz, Effect’iniz ilk render’dan ve değişen bağlımlılıklarla yeniden render’lardan sonra çalışacaktır.
useEffect(() => {
// ...
}, [a, b]); // a veya b farklıysa yeniden çalışır
Aşağıdaki örnekte, serverUrl
ve roomId
reaktif değerlerdir. Bu yüzden her ikisi de bağımlılık olarak belirtilmelidir. Sonuç olarak, aşağı doğru açılan menüden farklı bir oda seçmek ya da sunucu URL’ini değiştirmek sohbete yeniden bağlanılmasına neden olur. Ancak, message
Effect’te kullanılmadığından (ve bu yüzden bağımlılık da değil), mesajı düzenlemek sohbete yeniden bağlanmaya neden olmaz.
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); const [message, setMessage] = useState(''); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, roomId]); return ( <> <label> Sunucu URL'i:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>{roomId} odasına hoş geldiniz!</h1> <label> Mesajınız:{' '} <input value={message} onChange={e => setMessage(e.target.value)} /> </label> </> ); } export default function App() { const [show, setShow] = useState(false); 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="seyahat">Seyahat</option> <option value="muzik">Müzik</option> </select> <button onClick={() => setShow(!show)}> {show ? 'Sohbeti kapat' : 'Sohbeti aç'} </button> </label> {show && <hr />} {show && <ChatRoom roomId={roomId}/>} </> ); }
Effect’ten önceki state’e göre state’i güncelleme
Effect’ten önceki state’e göre state’i güncellemek istediğinizde, bir sorunla karşılaşabilirsiniz:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Sayacı saniyede bir artırmak istiyorsunuz...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... ancak `count`'u bağımlılık olarak belirtmek interval'i sıfırlayacaktır.
// ...
}
count
reaktif bir değer olduğundan, bağımlılık listesinde belirtilmek zorundadır. Ancak bu durum, Effect’in her count
değiştiğinde temizleme kurulum yapmasına neden olur. Bu ideal bir durum değildir.
Bunu düzeltmek için, c => c + 1
state güncelleyecisini setCount
’a iletin:
import { useState, useEffect } from 'react'; export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(c => c + 1); // ✅ State güncelleyicisi iletin }, 1000); return () => clearInterval(intervalId); }, []); // ✅ Artık count bir bağımlılık değildir return <h1>{count}</h1>; }
Artık count + 1
yerine c => c + 1
ilettiğimiz için, Effect’inizin count
’a bağımlı olmasına gerek yoktur. Bu çözümün sonucu olarak, Effect’iniz count
her değiştiğinde temizleme ve kurulum yapmasına gerek yoktur.
Gereksiz nesne bağımlılıklarını kaldırma
Eğer Effect’iniz render esnasında oluşturulan bir nesneye veya fonksiyona bağımlıysa, Effect çok sık çalışabilir. Örneğin bu Effect, options
nesnesi her render için farklı olduğundan her render’dan sonra yeniden sohbete bağlanır:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = { // 🚩 Bu nesne her yeniden render'dan sonra tekrar oluşturulur
serverUrl: serverUrl,
roomId: roomId
};
useEffect(() => {
const connection = createConnection(options); // Effect içinde kullanılır
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 Bunun neticesinde, bu bağımlılıklar yeniden render'da her zaman farklıdır
// ...
Render esnasında oluşturulan bir nesneyi bağımlılık olarak kullanmaktan kaçının. Bunun yerine nesneyi Effect içinde oluşturun:
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="seyahat">Seyahat</option> <option value="muzik">Müzik</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Şimdi options
nesnesini Effect içinde oluşturduğumuzdan, Effect sadece roomId
string’ine bağımlıdır.
Bu çözümle birlikte, input’a yazmak sohbete tekrar bağlanmayacaktır. Her render’da yeniden oluşturulan nesne aksine, roomId
gibi bir string siz onu başka bir değere eşitlemediğiniz sürece değişmez. Bağımlılıkları kaldırmak hakkında daha fazlasını okuyun.
Gereksiz fonksiyon bağımlılıklarını kaldırma
Eğer Effect’iniz render esnasında oluşturulan bir nesneye veya fonksiyona bağımlıysa, Effect çok sık çalışabilir. Örneğin bu Effect, createOptions
fonksiyonu her render’da farklı olduğundan her render’dan sonra yeniden sohbete bağlanır:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() { // 🚩 Bu fonksiyon her yeniden render'dan sonra sıfırdan tekrar oluşturulur
return {
serverUrl: serverUrl,
roomId: roomId
};
}
useEffect(() => {
const options = createOptions(); // Effect içinde kullanılır
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 Bunun neticesinde, bu bağımlılıklar yeniden render'da her zaman farklıdır
// ...
Her yeniden render’da sıfırdan bir fonksiyon oluşturmak kendi başına bir sorun değildir. Bunu optimize etmenize gerek yoktur. Ancak fonksiyonu Effect’inizin bağımlılığı olarak kullanırsanız, Effect’inizin her yeniden render’dan sonra yeniden çalışmasına neden olacaktır.
Render esnasında oluşturulan bir fonksiyonu bağımlılık olarak kullanmaktan kaçının. Bunun yerine Effect içinde bildirin:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; 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]); 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="seyahat">Seyahat</option> <option value="muzik">Müzik</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Şimdi createOptions
fonksiyonunu Effect içinde bildirdiğimizden, Effect sadece roomId
string’ine bağlıdır. Böylelikle input’u değiştirmek sohbete tekrar bağlanmayacaktır. Her render’da yeniden oluşturulan fonksiyon yerine, roomId
gibi bir string siz onu başka değere eşitlemediğiniz sürece değişmez. Bağımlılıkları kaldırmak hakkında daha fazlasını okuyun.
Effect’te nihai prop’ları ve state’i okuma
Varsayılan olarak, Effect’ten reaktif bir değer okuduğunuz zaman bu değeri bağımlılık olarak eklemeniz gerekmektedir. Bu, Effect’inizin o değer her değiştiğinde “tepki” vermesini sağlar. Çoğu bağımlılık için istediğiniz davranış budur.
Ancak bazen, nihai prop’ları ve state’i Effect bunlara “tepki” vermeden okumak isteyeceksiniz. Örneğin, her sayfa ziyareti için alışveriş sepetindeki ürünlerin sayısını kaydetmek istediğinizi hayal edin:
function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}
Ya url
her değiştiğinde yeni bir sayfa ziyareti kaydetmek istiyorsanız ancak sadece shoppingCart
değiştiğinde kaydetmek istemiyorsanız? Reaktivite kurallarını çiğnemeden shoppingCart
’ı bağımlılıklardan çıkartamazsınız. Ancak, Effect içinden çağırılsa bile bir kod parçasının yapılan değişikliklere “tepki” vermesini istemediğinizi ifade edebilirsiniz. useEffectEvent
Hook’u ile Effect Olayı bildirin ve shoppingCart
’ı okuyan kodu onun içine taşıyın:
function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});
useEffect(() => {
onVisit(url);
}, [url]); // ✅ Tüm bağımlılıklar bildirilmiş
// ...
}
Effect Olayları reaktif değillerdir ve Effect’inizin bağımlılıklarından kaldırılmalıdırlar. Bu, reaktif olmayan kodunuzu (prop’ların ve state’in nihai değerini okuyabildiğiniz) Effect’in içine koymanızı sağlar. shoppingCart
’ı onVisit
içinde okuyarak, shoppingCart
’ın Effect’inizi yeniden çalıştırmamasını sağlarsınız.
Sunucu ve kullanıcıda farklı içerikler gösterme
Uygulamanız sunucu render etme kullanıyorsa (ya direkt olarak ya da çatı kullanarak), bileşeniniz iki farklı ortamda render edilecektir. Sunucuda, başlangıç HTML’ini oluşturmak için render edecektir. Kullanıcıda, React olay yönetecilerini HTML’e eklemek için render etme kodunu yeniden çalıştıracaktır. Bu nedenle, hidrasyon işleminin çalışması için, ilk render çıktınızın kullanıcı ve sunucuda aynı olması gerekir.
Bazı nadir durumlarda, kullanıcıda farklı içerik göstermek isteyebilirsiniz. Örneğin, uygulamanız localStorage
’dan bazı veriler okuyorsa, bu işlemi sunucudan yapamaz. Bunu şu şekilde uygulayabilirsiniz:
function MyComponent() {
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
}, []);
if (didMount) {
// ... yalnızca kullanıcı JSX'i döndür ...
} else {
// ... ilk JSX'i döndür ...
}
}
Uygulama yüklenirken, kullanıcı ilk render çıktısını görecektir. Daha sonra, uygulama yüklendiğinde ve hidrasyon olduğunda, Effect’iniz çalışarak didMount
state’ini true
yapacak ve yeniden render tetikleyecektir. Bu kullanıcı-taraflı (client-side) render çıktısıyla değişecektir. Effect’ler sunucuda çalışmazlar, bu yüzden ilk server render’ı sırasında didMount
state’i false
’a eşittir.
Bu modeli idareli kullanın. Yavaş bir bağlantıya sahip kullanıcılar ilk içeriği oldukça uzun bir süre (potansiyel olarak saniyelerce) göreceğinden, bileşeninizin görünüşünde büyük değişiklikler yapmak istemezsiniz. Çoğu durumda, CSS ile koşullu olarak farklı şeyler göstererek buna ihtiyaç duymazsınız.
Sorun giderme
Bileşen DOM’a eklendiğinde Effect’im iki kere çalışıyor
Geliştirmede Strict modu açıkken, React kurulum ve temizleme işlemini asıl kurulumdan önce bir kere fazladan çalıştırır.
Bu, Effect mantığınızın doğru uygunlanıdığını doğrulayan bir stres testidir. Eğer bu, gözle görülebilir sorunlara neden oluyorsa, temizleme fonksiyonunuzda mantık hatası vardır. Temizleme fonksiyonu, kurulum fonksiyonunun yaptığı her şeyi durdurmalı veya geri almalıdır. Temel kural, kullanıcı bir kez çağrılan kurulum (son üründe olduğu gibi) ile kurulum → temizleme → kurulum sekansı (geliştirme sırasında olduğu gibi) arasındaki farkı ayırt etmemelidir.
Bunun nasıl hataları bulmanıza yardımcı olacağı ve mantığınızı nasıl düzelteceğiniz hakkında daha fazla bilgi edinin.
Effect’im her yeniden render’dan sonra çalışıyor
İlk olarak bağımlılık dizisini belirtmeyi unutup unutmadığınızı kontrol edin:
useEffect(() => {
// ...
}); // 🚩 Bağımlılık dizisi yok: her yeniden render'dan sonra yeniden çalışır!
Bağımlılık dizisini belirttiyseniz ancak Effect’iniz hala döngüde yeniden çalışyorsa, bunun nedeni bağımlılıklarınızdan birinin her yeniden render’da farklı olmasıdır.
Bağımlılıkları konsola manuel olarak yazdırarak bu hatayı ayıklayabilirsiniz:
useEffect(() => {
// ..
}, [serverUrl, roomId]);
console.log([serverUrl, roomId]);
Daha sonra konsoldaki farklı yeniden render’ların dizilerine sağ tıklayıp her ikisi için de “Global değişken olarak sakla“‘yı seçebilirsiniz. İlkinin temp1
olarak ve ikincinin temp2
olarak kaydedildiğini varsayarsak, her iki dizideki her bir bağımlılığın aynı olup olmadığını kontrol etmek için tarayıcı konsolunu kullanabilirsiniz:
Object.is(temp1[0], temp2[0]); // İlk bağımlılık diziler arasında aynı mı?
Object.is(temp1[1], temp2[1]); // İkinci bağımlılık diziler arasında aynı mı?
Object.is(temp1[2], temp2[2]); // ... ve diğer bağımlılıklar için ...
Her yeniden render’da farklı olan bağımlılığı bulduğunzda, genellikle şu yollardan biriyle düzeltebilirsiniz:
- Effect’ten önceki state’e göre state’i güncelleme
- Gereksiz nesne bağımlılıklarını kaldırma
- Gereksiz fonksiyon bağımlılıklarını kaldırma
- Effect’te nihai prop’ları ve state’i okuma
Son çare olarak (bu yöntemler yardımcı olmadıysa), useMemo
veya useCallback
(fonksiyonlar için) kullanın.
Effect’im sonsuz bir döngüde sürekli çalışıyor
Effect’iniz sonsuz bir döngüde çalışıyorsa, şu iki şey doğru olmak zorundadır:
- Effect’iniz bir state’i güncelliyor.
- O state, Effect’in bağımlılıklarının değişmesine neden olan bir yeniden render tetikliyor.
Sorunu çözmeye başlamadan önce, Effect’inizin harici bir sisteme (DOM, ağ veya üçüncü parti widget gibi) bağlanıp bağlanmadığını kendinize sorun. Effect’iniz neden state’i değiştiriyor? Harici sistem ile senkronizasyon mu yapıyor? Yoksa uygulamanızın veri akışını Effect ile mi yönetmeye çalışıyorsunuz?
Harici bir sistem yoksa, Effect’i tamamen kaldırmanın mantığınızı basitleştirip basitleştirmeyeceğine bakın.
Eğer gerçekten harici bir sistem ile senkronizasyon yapıyorsanız, Effect’inizin neden ve hangi koşullarda state’i güncellemesi gerektiğini düşünün. Bileşeninizin görsel çıktısını etkileyen bir değişiklik mi oldu? Render sırasında kullanılmayan bazı verileri takip etmeniz gerekiyorsa, ref (yeniden render tetiklemez) daha uygun olabilir. Effect’inizin state’i gereğinden fazla güncellemediğini (ve yeniden render’lar tetiklemediğini) doğrulayın.
Son olarak, Effect’iniz state’i doğru zamanda güncelliyorsa ancak yine de bir döngü söz konusuysa, bunun nedeni, state güncellemesinin Effect’in bağımlılıklarından birinin değişmesine neden olmasıdır. Bağımlılık değişikliklerinden kaynaklı hataların nasıl ayıklanacağını okuyun.
Temizleme mantığım bileşenim DOM’dan kaldırılmasa bile çalışıyor
Temizleme fonksiyonu sadece DOM’dan kaldırılma sırasında değil, değişen bağımlılıklarla her yeniden render’dan önce de çalışır. Ek olarak, geliştirme aşamasında, React kurulum+temizleme fonksiyonlarını bileşen DOM’a eklendikten hemen sonra bir kez daha çalıştırır.
Bir temizleme kodunuz var ancak kurulum kodunuz yoksa, bu genellike kötü kokan bir koddur (code smell):
useEffect(() => {
// 🔴 Kaçının: Kurulum mantığı olmadan temizleme mantığı var
return () => {
doSomething();
};
}, []);
Temizleme mantığınız kurulum mantığıyla “simetrik” olmalı ve kurulumun yaptığı her şeyi durdurmalı veya geri almalıdır:
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
Effect yaşam döngüsünün bileşenin yaşam döngüsünden ne kadar farklı olduğunu öğrenin.
Effect’im görsel bir şey yapıyor ve çalışmadan önce bir titreme görüyorum
Effect’iniz tarayıcının ekranı çizmesini engelliyorsa, useEffect
’i useLayoutEffect
ile değiştirin. Bunu yapmaya Effect’lerin büyük çoğunluğu için ihtiyaç duyulmaması gerektiğini unutmayın. Buna yalnızca Effect’inizi tarayıcı ekranı çizmeden önce çalıştırmanız çok önemliyse ihtiyacanız olacak: örneğin, bir tooltip’ini kullanıcı görmeden önce ölçmek ve konumlandırmak için.