Effect’e İhtiyacımız Olmayabilir
⚛️

Effect’e İhtiyacımız Olmayabilir

Tags
useEffect
react hooks
React.js
js
Growth Status
🌱 Seedling
Intended Audience
Beginner- Intermediate React Developer
Published
December 29, 2022
Author
Zeynep Şimşek
⚛️
React
React ile bir uygulama yazarken useEffect’in yanlış kullanımı veya ihtiyaç duyulmayan durumlarda kullanılması, performans ve okunaklılık açısından sorunlar oluşturabilir, oluşturuyor da.
David Khourshid bu durumu anlatırken de []); yapısına “Frustrated React Developer smiley” diyor. Tamamen katılıyorum. Videoyu izledikten sonra React’ın https://beta.reactjs.org üzerinden dökümanlarını inceleyip konuyu detaylı bir incelemek istedim ki bunu da paylaşayım daha fazla kişi kurtulsun bu “EFFECT” lerden diye. Kafa karışıklığı olmasın diye etki diye çevirmeyip useEffect’teki Effect kelimesini kullanmaya devam edeceğim.
Efektler, React bağlamından bir kaçış kapısıdır. React'in "dışına çıkmanıza" ve bileşenlerinizi React olmayan bir widget, network veya tarayıcı DOM'u gibi bazı harici sistemlerle senkronize etmenize izin verir. İlgili harici bir sistem yoksa (yani, bazı proplar ya da state değiştiğinde bir bileşenin state’ini güncellemek istiyorsanız), bir Effect’ e ihtiyacınız olmamalıdır. Gereksiz Effect’leri kaldırmak, kodunuzun takip edilmesini kolaylaştıracak, çalıştırılmasını hızlandıracak ve hataya daha az açık hale getirecektir.

Gereksiz Effectlerden nasıl kaçılır?

Efektlere ihtiyaç duymadığınız iki yaygın durum vardır:
  • En sık yapıldığını düşündüğüm Render için datayı değiştirmek. Mesela, bir listeyi görüntülemeden önce filtrelemek istiyorsunuz diyelim. Liste değiştiğinde bir state değişkenini güncelleyen bir Efekt yazmak isteyebilirsiniz. Ancak bu verimsizdir. Bileşeninizin state’ini güncellediğinizde Reacti ekranda ne olması gerektiğini hesaplamak için önce komponent fonksiyonlarınızı çağırır. Ardından React, ekranı güncelleyerek bu değişiklikleri DOM’a işleyecektir. Sonra Effectlerinizi çalıştıracaktır. Eğer Effect’iniz ayrıca durumu tekrar güncellerse, bu tüm süreci yeniden başlatır. Gereksiz renderlardan kaçınmak için bileşenlerinizin en üst düzeyindeki tüm verileri güncelleyin. Bu kod, proplar veya state’iniz değiştiğinde otomatik olarak yeniden çalışacaktır.
  • Kullanıcı eventlerini çalıştırmak için Effectlere ihtiyacınız yok. Örneğin, bir /api/buy POST isteği göndermek ve kullanıcı bir ürün satın aldığında bir bildirim göstermek istediğinizi varsayalım. Satın Al düğmesine tıklayın event handlerda, tam olarak ne olduğunu bilirsiniz. Ama bir Efekt çalıştığında, kullanıcının ne yaptığını (örneğin, hangi düğmenin tıklandığını) bilemezsiniz. Bu nedenle, kullanıcı olaylarını genellikle karşılık gelen event handlerlarda işleyeceksiniz.
  • Harici sistemlerle senkronize etmek için Effects'e ihtiyacınız var. Örneğin, bir jQuery widgetını React state’i ile senkronize halde tutan bir Effect yazabilirsiniz. Efektler ile de veri çekebilirsiniz: örneğin, arama sonuçlarını mevcut arama sorgusuyla senkronize edebilirsiniz. Modern çerçevelerin, doğrudan bileşenlerinize Efektler yazmaktan daha verimli yerleşik veri çekme mekanizmaları sunduğunu unutmayın.
Konunun daha iyi anlaşılması için, bazı yaygın somut örneklere bakalım!

Props ya da State’e Bağlı Olarak State Güncelleme

İki state değişkenine sahip bir komponentiniz olduğunu varsayalım: firstName ve lastName. Bunları birleştirerek onlardan bir fullName hesaplamak istiyorsunuz. Ayrıca, firstName veya lastName değiştiğinde fullName'in güncellenmesini istersiniz. İlk içgüdünüz, bir fullName state değişkeni eklemek ve onu bir Effect’te güncellemek olabilir:
function Form() { const [firstName, setFirstName] = useState('Taylor'); const [lastName, setLastName] = useState('Swift'); // 🔴 Avoid: redundant state and unnecessary Effect const [fullName, setFullName] = useState(''); useEffect(() => { setFullName(firstName + ' ' + lastName); }, [firstName, lastName]); // ... }
Bu şekilde kullanmak hem daha karmaşıktır, aynı zamanda verimsizdir de: fullName için eski bir state’le tüm render’ı tekrar çalıştırır, ardından hemen güncellenmiş state’le yeniden işler. Hem state değişkenini hem de Effect’i kaldırın:
Bir değer mevcut proplardan veya state’ten hesaplanabiliyorsa, onu state’e koymayın. Bunun yerine, render sırasında hesaplayın. Bu, kodunuzu daha hızlı hale getirir (ekstra "basamaklı" güncellemelerden kaçınırsınız), daha basit hale getirir (bazı kodları kaldırırsınız) ve daha az hataya açık hale getirir (birbiriyle senkronize olmayan farklı state değişkenlerinin neden olduğu hataları önlersiniz). Bu yaklaşım size yeni geliyorsa, Thinking in React'te neyin state’e girmesi gerektiğine dair bilgilendirme var, inceleyebilirsiniz.

Pahalı Hesaplama İşlemlerini Cache’leme

Bu komponent, aldığı todos'u prop'lar tarafından alarak ve filter prop'a göre filtreleyerek görünürTodos'u hesaplar. Sonucu bir state değişkeninde saklamak ve bir Efektte güncellemek isteyebilirsiniz:
function TodoList({ todos, filter }) { const [newTodo, setNewTodo] = useState(''); // 🔴 Avoid: redundant state and unnecessary Effect const [visibleTodos, setVisibleTodos] = useState([]); useEffect(() => { setVisibleTodos(getFilteredTodos(todos, filter)); }, [todos, filter]); // ... }
Önceki örnekte olduğu gibi, bu hem gereksiz hem de verimsizdir. İlk olarak, state’i ve Efekti kaldırın:
Bu, React'e, todos veya filter değişmedikçe içerdeki fonksiyonun yeniden çalışmasını istemediğinizi söyler. React, ilk oluşturma sırasında getFilteredTodos() öğesinin dönüş değerini hatırlayacaktır. Bir sonraki oluşturma sırasında todos veya filterların farklı olup olmadığını kontrol edecektir. önceki render ile aynıysa, useMemo sakladığı son sonucu döndürür. Ancak farklılarsa, React useMemo ile sarılmış fonksiyonu yeniden çağırır (ve önceki sonuç yerine bu son sonucu depolar).
useMemo' ya ile sarmalanan fonksiyon, render sırasında çalışır, bu nedenle bu yalnızca pure hesaplamalar için çalışır.
Derin Dalış - Hesaplamanın pahalı olduğunu nasıl anlarız?
Genel olarak, binlerce nesne oluşturmuyorsanız veya binlerce döngü yapmıyorsanız, muhtemelen pahalı değildir. Daha fazla güven duymak istiyorsanız, bir kod parçasında harcanan süreyi ölçmek için bir console.log ekleyebilirsiniz:
console.time('filter array'); const visibleTodos = getFilteredTodos(todos, filter); console.timeEnd('filter array');
Ölçtüğünüz etkileşimi gerçekleştirin (örneğin, input sağlayarak). Daha sonra konsolunuzda filter array: 0.15ms gibi loglar göreceksiniz. Toplam kaydedilen süre önemli bir miktara ulaşırsa (örneğin, 1 ms veya daha fazla), bu hesaplamayı memory’ de tutmak mantıklı olabilir. Bir deney olarak, o etkileşim için kaydedilen toplam sürenin azalıp azalmadığını doğrulamak için hesaplamayı useMemo'ya sarabilirsiniz:
console.time('filter array'); const visibleTodos = useMemo(() => { return getFilteredTodos(todos, filter); // Skipped if todos and filter haven't changed }, [todos, filter]); console.timeEnd('filter array');
useMemo, ilk render’ı daha hızlı yapmaz. Yalnızca güncellemelerle ilgili gereksiz çalıştırmaları atlamanıza yardımcı olur.
Makinenizin muhtemelen kullanıcılarınızdan daha hızlı olduğunu unutmayın, bu nedenle performansı yapay bir yavaşlama ile test etmek iyi bir fikirdir. Örneğin, Chrome bunun için bir CPU Throttling(Kısma) seçeneği sunar.
Ayrıca, geliştirmede performansı ölçmenin size en doğru sonuçları vermeyeceğini unutmayın. (Örneğin, Strict Mode açıkken, her bileşenin bir yerine iki kez işlendiğini göreceksiniz.) En doğru zamanlamaları elde etmek için uygulamanızı production için oluşturun ve kullanıcılarınız gibi bir cihazda test edin.
 

Prop değiştiğinde tüm state’i sıfırlamak

Burda ProfilePage bileşeni, bir userId’i prop aracılığı ile alır. Sayfa bir comment inputu içerir ve siz onun değerini tutmak için bir comment state değişkeni kullanırsınız. Bir gün bir sorun fark ettiniz: Bir profilden diğerine geçtiğinizde comment state’i sıfırlanmıyor. Sonuç olarak, yanlışlıkla yanlış bir kullanıcının profiline yorum gönderiyorsunuz. Bu sorunu çözmek için, userId her değiştiğinde yorum durumu değişkenini temizlemek istiyorsunuz:
export default function ProfilePage({ userId }) { const [comment, setComment] = useState(''); // 🔴 Avoid: Resetting state on prop change in an Effect useEffect(() => { setComment(''); }, [userId]); // ... }
Bu verimsizdir, çünkü ProfilePage ve childrenları önce eski değerle render edecek ve sonra tekrar render edecektir. Aynı zamanda karmaşıktır, çünkü bunu ProfilePage içinde bir state’i olan her komponentte yapmanız gerekir. Örneğin, comment UI iç içeyse iç içe olan commentlerin state’ini de temizlemek isteyebilirsiniz.
Bunun yerine, React'e açık bir key vererek her kullanıcının profilinin kavramsal olarak farklı bir profil olduğunu söyleyebilirsiniz. Komponentinizi ikiye bölün ve dış komponentten iç komponente bir key niteliği iletin:
Normalde React, aynı komponent aynı yerde render edildiğinde state’i korur. UserId'yi Profile bileşenine bir key olarak ileterek, React'ten farklı userId'lere sahip iki Profile komponentini herhangi bir state’i paylaşmaması gereken iki farklı bileşen olarak ele almasını istiyorsunuz. Key (userId olarak ayarladığınız) her değiştiğinde, React DOM'u yeniden oluşturur ve Profile komponentinin ve tüm alt childrenların durumunu sıfırlar. Sonuç olarak, profiller arasında gezinirken comment alanı otomatik olarak temizlenecektir.
Bu örnekte, yalnızca dış ProfilePage komponentinin dışa aktarıldığını ve projedeki diğer dosyalara görünür olduğunu unutmayın. ProfilePage'i oluşturan komponentlerin ona key iletmesi gerekmez: userId'yi normal bir prop olarak iletirler. ProfilePage'in onu iç Profil komponentine key olarak geçirmesi, bir uygulama ayrıntısıdır.

Prop değiştiğinde state’i değiştirmek

Bazen, bir prop değişikliğinde state’in bir kısmını sıfırlamak veya değiştirmek isteyebilirsiniz, ancak hepsini değil.
Bu List komponenti, bir prop olarak bir item listesi alır ve seçili itemi selection state değişkeninde tutar. Items prop’u farklı bir dizi aldığında seçimi null olarak sıfırlamak istiyorsunuz:
function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); // 🔴 Avoid: Adjusting state on prop change in an Effect useEffect(() => { setSelection(null); }, [items]); // ... }
Bu da çok ideal değil, items her değiştiğinde, List ve childrenları ilk başta eski bir selection state’i ile render edilecektir. Ardından React, DOM'u güncelleyecek ve Efektleri çalıştıracaktır. Son olarak, setSelection(null) çağrısı, tüm bu süreci yeniden başlatarak, Listin ve childrenların yeniden oluşturulmasına neden olur.
Efekti silerek başlayın. Bunun yerine, render sırasında doğrudan state’i ayarlayın:
Önceki renderlardan bilgi depolamayı anlamak zor olabilir, ancak aynı state’i bir effectte güncellemekten daha iyidir. Yukarıdaki örnekte, setSelection bir render sırasında doğrudan çağırılır. React, return ifadesiyle dönüş yapmadan öncesinde List ’i yeniden render edecektir. Bu noktada, React henüz List childrenlarını render etmemiş ya da DOM’u henüz güncellememiştir, dolayısıyla bu, List childrenlarının eski selection state’ini render etmeyi atlamasını sağlar .
Render sırasında bir komponenti güncellediğinizde, React döndürülen JSX'i atar ve renderi hemen yeniden dener. Çok yavaş ardışık yeniden renderlardan kaçınmak için React, bir render sırasında yalnızca aynı komponentin state’ini güncellemenize izin verir. Render sırasında başka bir komponentin state’ini güncellerseniz hata görürsünüz. Döngülerden kaçınmak için items !== prevItems gibi bir koşul gereklidir. State’i bu şekilde değiştirebilirsiniz, ancak diğer tüm yan etkiler (DOM'u değiştirmek veya timeout ayarlamak gibi), komponentlerinizi öngörülebilir tutmak için event handler veya Efektlerde kalmalıdır.
Bu pattern bir Efektten daha verimli olsa da, çoğu bileşenin buna da ihtiyacı olmaması gerekir. Nasıl yaparsanız yapın, state’i props’a göre değiştirmek ya da başka bir state’e göre değiştirmek, veri akışımızın anlaşılmasını ve hata ayıklamayı zorlaştırır. Her zaman tüm state'i bir key ile sıfırlayamayacağınızı veya bunun yerine render sırasında her şeyi hesaplayıp hesaplayamayacağınızı kontrol edin. Örneğin, seçilen itemi saklamak (ve sıfırlamak) yerine, seçilen item ID’sini saklayabilirsiniz:
Artık state’i "değiştirmeye" hiç gerek yok. Seçilen ID’ye sahip item listedeyse, seçili kalır. Değilse, render sırasında hesaplanan selection, eşleşen bir item bulunmadığından boş olacaktır. Bu davranış biraz farklıdır, ancak muhtemelen daha iyidir çünkü itemlerde yapılan değişikliklerin çoğu artık seçimi korumaktadır. Ancak, aşağıdaki mantığın tamamında seçimi kullanmanız gerekir, çünkü selectedId'ye sahip bir item olmayabilir.

Event Handlerlar arasında akışı paylaşma

Diyelim ki her ikisi de o ürünü satın almanıza izin veren iki butonu olan (Satın Al ve Ödeme Yap) bir ürün sayfanız var. Kullanıcı ürünü sepete her koyduğunda bir bildirim göstermek istiyorsunuz. showNotification() çağrısının her iki butonun da ayrı ayrı tıklanma fonksiyonlarına eklenmesi tekrarlayıcı bir his verir, bu nedenle bu mantığı bir Efekte yerleştirmek isteyebilirsiniz:
function ProductPage({ product, addToCart }) { // 🔴 Avoid: Event-specific logic inside an Effect useEffect(() => { if (product.isInCart) { showNotification(`Added ${product.name} to the shopping cart!`); } }, [product]); function handleBuyClick() { addToCart(product); } function handleCheckoutClick() { addToCart(product); navigateTo('/checkout'); } // ... }
Buradaki Effect gereksizdir. Ayrıca büyük olasılıkla hatalara neden olacaktır. Örneğin, uygulamanızın sayfa yeniden yüklemeleri arasında alışveriş sepetini "hatırladığını" varsayalım. Sepete bir kez ürün ekler ve sayfayı yenilerseniz, bildirim tekrar görünecektir. Bu ürünün sayfasını her yenilediğinizde görünmeye devam edecektir. Bunun nedeni, product.isInCart'ın sayfa yüklemesinde zaten true olacağından, yukarıdaki Effect showNotification()'ı çağıracaktır.
Bazı kodların bir Efektte mi yoksa bir event handlerda mi olması gerektiğinden emin olmadığınızda, bu kodun neden çalıştırılması gerektiğini kendinize sorun. Efektleri yalnızca komponent kullanıcıya gösterildiği için çalışması gereken kodlar için kullanın. Bu örnekte bildirim, sayfa görüntülendiği için değil, kullanıcı düğmeye bastığı için görünmelidir! Efekti silin ve paylaşılan mantığı her iki event handlerdan çağırdığınız bir fonksiyona koyun:
Bu hem gereksiz Efekti kaldırır hem de hatayı düzeltir.

Post isteği göndermek

Bu Form bileşeni, iki tür POST isteği gönderir. mount edildiğinde bir analitik eventi gönderir. Formu doldurup Gönder düğmesine tıkladığınızda, /api/register end pointine bir POST isteği gönderecektir:
function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); // ✅ Good: This logic should run because the component was displayed useEffect(() => { post('/analytics/event', { eventName: 'visit_form' }); }, []); // 🔴 Avoid: Event-specific logic inside an Effect const [jsonToSubmit, setJsonToSubmit] = useState(null); useEffect(() => { if (jsonToSubmit !== null) { post('/api/register', jsonToSubmit); } }, [jsonToSubmit]); function handleSubmit(e) { e.preventDefault(); setJsonToSubmit({ firstName, lastName }); } // ... }
Bir önceki örnekte olduğu gibi aynı kriterleri uygulayalım.
Analitik POST isteği, bir Effektte kalmalıdır. Bunun nedeni, analytics olayını gönderme nedeninin formun görüntülenmiş olmasıdır. (Development sırasında iki kez tetiklenir, ancak bununla nasıl başa çıkılacağını görmek için buraya bakın.)
Ancak /api/register POST isteği, görüntülenen formdan kaynaklanmaz. İsteği yalnızca belirli bir anda göndermek istiyorsunuz: kullanıcı düğmeye bastığında. Yalnızca söz konusu etkileşimde gerçekleşmelidir. İkinci Efekti silin ve bu POST isteğini event handlera taşıyın:
Bir event handler mı yoksa bir Etkiye mi mantık koyacağınızı seçtiğinizde, cevaplamanız gereken asıl soru, kullanıcının bakış açısından bunun ne tür bir mantık olduğudur. Bu mantık belirli bir etkileşimden kaynaklanıyorsa, bunu event handlerda tutun. Kullanıcının komponentini ekranda görmesinden kaynaklanıyorsa, bunu Efekt'te tutun.

hesaplama zincirleri

Bazen, her biri bir state parçasını başka bir duruma göre ayarlayan Efektleri zincirlemek isteyebilirsiniz:
function Game() { const [card, setCard] = useState(null); const [goldCardCount, setGoldCardCount] = useState(0); const [round, setRound] = useState(1); const [isGameOver, setIsGameOver] = useState(false); // 🔴 Avoid: Chains of Effects that adjust the state solely to trigger each other useEffect(() => { if (card !== null && card.gold) { setGoldCardCount(c => c + 1); } }, [card]); useEffect(() => { if (goldCardCount > 3) { setRound(r => r + 1) setGoldCardCount(0); } }, [goldCardCount]); useEffect(() => { if (round > 5) { setIsGameOver(true); } }, [round]); useEffect(() => { alert('Good game!'); }, [isGameOver]); function handlePlaceCard(nextCard) { if (isGameOver) { throw Error('Game already ended.'); } else { setCard(nextCard); } } // ...
In this case, it’s better to calculate what you can during rendering, and adjust the state in the event handler:
 
Bu kodla ilgili iki sorun var.
Birinci sorun, çok verimsiz olmasıdır: komponent (ve childrenları), zincirdeki her set çağrısı arasında yeniden işlemek zorundadır. Yukarıdaki örnekte, en kötü durumda (setCard → render → setGoldCardCount → render → setRound → render → setIsGameOver → render), aşağıdaki ağacın üç gereksiz yeniden render edilmesi vardır.
Yavaş olmasa bile, kodunuz geliştikçe yazdığınız "zincirin" yeni gereksinimlere uymadığı durumlarla karşılaşacaksınız. Oyun hamlelerinizin geçmişinde gezinmek için bir yol eklediğinizi hayal edin. Bunu, her state değişkenini geçmişten bir değere güncelleyerek yaparsınız. Ancak, card state’ini geçmişten bir state’e ayarlamak, Etki zincirini tekrar tetikler ve gösterdiğiniz verileri değiştirir. Bunun gibi kodlar genellikle katı ve kırılgandır.
Bu durumda, render sırasında neler yapabileceğinizi hesaplamak ve durumu event handlerda ayarlamak daha iyidir:
Bu şekilde çok daha verimli. Ayrıca, oyun geçmişini görüntülemenin bir yolunu uygularsanız, artık diğer tüm stateleri ayarlayan Etki zincirini tetiklemeden her state değişkenini geçmişten bir harekete ayarlayabileceksiniz. Birkaç event handler arasında mantığı yeniden kullanmanız gerekirse, bir fonksiyonu çıkarabilir ve bu event handlerlardan çağırabilirsiniz.
Event Handlerların içinde durumun bir anlık görüntü gibi davrandığını unutmayın. Örneğin, setRound(round + 1) öğesini çağırdıktan sonra bile, round değişkeni kullanıcının buttonı tıkladığı andaki değeri yansıtacaktır. Hesaplamalar için sonraki değeri kullanmanız gerekirse, bunu const nextRound = round + 1 gibi manuel olarak tanımlayın.
Bazı durumlarda, bir sonraki durumu doğrudan event handlerda hesaplayamazsınız. Örneğin, bir sonraki açılır listenin seçeneklerinin önceki açılır listenin seçilen değerine bağlı olduğu birden fazla açılır liste içeren bir form hayal edin. Ardından, network ile senkronize ettiğiniz için veri getiren bir Efekt zinciri uygundur.

Uygulamayı Başlatma

Bazı işlemler, uygulama yüklendiğinde yalnızca bir kez çalışmalıdır. En üst düzey bileşendeki bir Efekte yerleştirebilirsiniz:
function App() { // 🔴 Avoid: Effects with logic that should only ever run once useEffect(() => { loadDataFromLocalStorage(); checkAuthToken(); }, []); // ... }
Ancak, development aşamasında iki kez çalıştığını hemen keşfedeceksiniz. Bu, sorunlara neden olabilir; örneğin, fonksiyon iki kez çağrılacak şekilde tasarlanmadığı için id doğrulama belirtecini geçersiz kılabilir. Genel olarak, komponentleriniz yeniden mount edilmeye karşı dayanıklı olmalıdır. Bu, üst düzey App komponentinizi içerir. Productionda pratikte hiçbir zaman yeniden mount edilemese de, tüm komponentlerde aynı kısıtlamalara uyulması, kodu taşımayı ve yeniden kullanmayı kolaylaştırır. Bazı işlemlerin bileşen mount başına bir kez yerine uygulamanın yüklenmesi başına bir kez çalıştırılması gerekiyorsa, zaten yürütülüp yürütülmediğini izlemek için bir üst düzey değişken ekleyebilir ve her zaman yeniden çalıştırmayı atlayabilirsiniz:
Modül başlatma sırasında ve uygulama render edilmeden önce de çalıştırabilirsiniz:
En üst düzeydeki kod, komponentiniz içe aktarıldığında, render olmasa bile bir kez çalışır. Rastgele kompnentleri içe aktarırken yavaşlama veya şaşırtıcı davranışlardan kaçınmak için bu patterni aşırı kullanmayın. Uygulama genelinde başlatma mantığını App.js gibi root komponent modüllerinde veya uygulamanızın giriş noktası modülünde tutun.

Ana komponentleri state değişiklikleri hakkında bilgilendirme

Diyelim ki, true veya false olabilen dahili bir isOn durumuna sahip bir Toggle bileşeni yazıyorsunuz. Toggle yapmanın birkaç farklı yolu vardır (tıklayarak veya sürükleyerek). Toggle dahili state’i her değiştiğinde üst komponente bildirimde bulunmak istiyorsunuz, böylece bir onChange eventini açığa çıkarıyor ve onu bir Effect'ten çağırıyorsunuz:
function Toggle({ onChange }) { const [isOn, setIsOn] = useState(false); // 🔴 Avoid: The onChange handler runs too late useEffect(() => { onChange(isOn); }, [isOn, onChange]) function handleClick() { setIsOn(!isOn); } function handleDragEnd(e) { if (isCloserToRightEdge(e)) { setIsOn(true); } else { setIsOn(false); } } // ... }
 
Daha önce olduğu gibi, bu ideal değil. Toggle önce stateini günceller ve React ekranı günceller. Ardından React, bir üst komponentten iletilen onChange fonksiyonu nu çağıran Effect'i çalıştırır. Şimdi ana komponent, başka bir işleme geçişi başlatarak kendi durumunu güncelleyecektir. Bunun yerine her şeyi tek geçişte yapmak daha iyi olur.
Efekti silin ve bunun yerine aynı event handler içindeki her iki komponentin durumunu güncelleyin:
Bu yaklaşımla, hem Toggle komponenti hem de üst komponenti event sırasında statelerini günceller. React, farklı komponentlerden toplu güncellemelere birlikte yanıt verir, böylece sonucu gösteren render bir kez çalışır.
Ayrıca state’i tamamen kaldırabilir ve bunun yerine üst bileşenden isOn alabilirsiniz:
 
"State’i yukarı taşıma", parentın kendi state’ini değiştirerek ana komponenti tam olarak kontrol etmesini sağlar. Bu, ana komponentin daha fazla işlem içermesi gerekeceği, ancak genel olarak endişelenecek daha az durum olacağı anlamına gelir. İki farklı state değişkenini senkronize tutmaya çalıştığınızda, bunun yerine state’i taşımayı denemek için bir işarettir!

Parent’a data aktarma

Bu Child bileşeni, bazı verileri alır ve ardından bir Effect'teki Parent bileşenine iletir:
function Parent() { const [data, setData] = useState(null); // ... return <Child onFetched={setData} />; } function Child({ onFetched }) { const data = useSomeAPI(); // 🔴 Avoid: Passing data to the parent in an Effect useEffect(() => { if (data) { onFetched(data); } }, [onFetched, data]); // ... }
React'te, veriler ana komponentten bunların childrenlarına aktarılır. Ekranda yanlış bir şey gördüğünüzde, hangi bileşenin yanlış proptan geçtiğini veya yanlış statete olduğunu bulana kadar komponent zincirinde yukarı çıkarak bilginin nereden geldiğini takip edebilirsiniz. Alt komponentler, Effects'te üst komponentlerin state’ini güncellediğinde, veri akışının izlenmesi çok zor hale gelir. Hem alt komponent hem de üst komponent aynı verilere ihtiyaç duyduğundan, üst komponentin bu verileri getirmesine izin verin ve bunun yerine alt komponente aktarın:
Bu daha basittir ve veri akışının tahmin edilebilir olmasını sağlar: veri parenttan children’a doğru akar.

Harici bir bilgi kaynağına abone olma

Sometimes, your components may need to subscribe to some data outside of the React state. This data could be from a third-party library or a built-in browser API. Since this data can change without React’s knowledge, you need to manually subscribe your components to it. This is often done with an Effect, for example:
Bazen, komponentlerinizin React state’i dışında bazı verilere abone olması gerekebilir. Bu veriler, bir üçüncü taraf js kütüphanelerinden veya yerleşik bir tarayıcı API'sinden olabilir. Bu veriler React'in bilgisi olmadan değişebileceğinden, komponentlerinizi buna manuel olarak abone etmeniz gerekir. Bu genellikle bir Efekt ile yapılır, örneğin:
function useOnlineStatus() { // Not ideal: Manual store subscription in an Effect const [isOnline, setIsOnline] = useState(true); useEffect(() => { function updateState() { setIsOnline(navigator.onLine); } updateState(); window.addEventListener('online', updateState); window.addEventListener('offline', updateState); return () => { window.removeEventListener('online', updateState); window.removeEventListener('offline', updateState); }; }, []); return isOnline; } function ChatIndicator() { const isOnline = useOnlineStatus(); // ... }
Bileşen burada harici bir veri deposuna abone olur (bu durumda tarayıcı navigator.onLine API). Bu API sunucuda bulunmadığından (bu nedenle ilk HTML'yi oluşturmak için kullanılamaz), başlangıçta state true olarak ayarlanır. Bu veri deposunun değeri tarayıcıda her değiştiğinde, komponent stateini günceller.
Bunun için Efektlerin kullanılması yaygın olsa da, React'in bunun yerine tercih edilen harici bir mağazaya abone olmak için amaca yönelik oluşturulmuş bir Hook'u vardır. Efekti silin ve bir useSyncExternalStore çağrısıyla değiştirin:
Bu yaklaşım, değiştirilebilir verileri bir Efekt ile React durumuna manuel olarak senkronize etmekten daha az hataya açıktır. Tipik olarak, yukarıdaki useOnlineStatus() gibi özel bir Hook yazarsınız, böylece bu kodu tek tek bileşenlerde tekrarlamanız gerekmez. React bileşenlerinden harici mağazalara abone olma hakkında daha fazla bilgi edinin.

Veri Getirmek

Birçok uygulama, veri getirmeyi başlatmak için Efektleri kullanır. Bunun gibi bir veri getirmek için Efekti yazmak oldukça yaygındır:
function SearchResults({ query }) { const [results, setResults] = useState([]); const [page, setPage] = useState(1); useEffect(() => { // 🔴 Avoid: Fetching without cleanup logic fetchResults(query, page).then(json => { setResults(json); }); }, [query, page]); function handleNextPageClick() { setPage(page + 1); } // ... }
Bu fonksiyonu bir event handler’a taşımanız gerekmez.
Bu, mantığı event handler’lara yerleştirmeniz gereken önceki örneklerle bir çelişki gibi görünebilir! Ancak, veri getirmenin ana nedeninin yazma eventi olmadığını düşünün. Arama girişleri genellikle URL'den önceden doldurulur ve kullanıcı girişe dokunmadan Geri ve İleri gidebilir. page ve query'nin nereden geldiği önemli değil. Bu bileşen görünürken, geçerli page ve query göre resultsın ağdan gelen verilerle senkronize olmasını istersiniz. Bu yüzden bu bir Effecttir.
Ancak, yukarıdaki kodda bir hata var. Hızlı bir şekilde "hello" yazdığınızı hayal edin. Sonra sorgu "h"den "he"ye, "hel", "hell"'eve "hello"ya değişecektir. Bu, ayrı veri getirmeleri başlatır, ancak yanıtların hangi sırayla geleceği konusunda bir garanti yoktur. Örneğin, "hell" yanıtı, "hello" yanıtından sonra gelebilir. En son setResults() öğesini çağıracağından, yanlış arama sonuçlarını görüntülemiş olacaksınız. Buna “race condition”(yarış durumu) denir: iki farklı istek birbiriyle "yarıştı"(race) ve beklediğinizden farklı bir sırada geldi.
Yarış koşulunu düzeltmek için, eski yanıtları yoksaymak için bir temizleme işlevi(clean up function) eklemeniz gerekir:
Bu şekilde kullanmak, Efektiniz verileri getirdiğinde, istenen son yanıt dışındaki tüm yanıtların yok sayılmasını sağlar.
Veri getirmeyi uygulamadaki tek zorluk yarış koşullarını ele almak değildir. Yanıtları nasıl önbelleğe alacağınızı (böylece kullanıcı Geri'yi tıklatabilir ve bir spinner yerine anında önceki ekranı görebilir), bunların sunucuda nasıl alınacağını (böylece server-rendered ilk HTML'nin spinner yerine getirilen içerik olur) ve ağ waterfallarından nasıl kaçınılacağı (böylece veri getirmesi gereken bir children, başlamadan önce üzerindeki her parentın verilerini getirmeyi bitirmesini beklemek zorunda kalmaz). Bu sorunlar, yalnızca React için değil, herhangi bir UI kitaplığı için geçerlidir. Bunları çözmek önemsiz değildir, bu nedenle modern frameworkler, Efektleri doğrudan bileşenlerinize yazmaktan daha verimli yerleşik veri alma mekanizmaları sağlar.
Bir framework kullanmıyorsanız (ve kendi framework’ünüzü oluşturmak istemiyorsanız) ancak Effects'ten veri almayı daha ergonomik hale getirmek istiyorsanız, getirme mantığınızı bu örnekteki gibi özel bir Hook'a çıkarmayı düşünün:
Muhtemelen hata işleme ve içeriğin yüklenip yüklenmediğini izlemek için biraz mantık eklemek isteyeceksiniz. Bunun gibi bir Hook'u kendiniz oluşturabilir veya React ekosisteminde zaten mevcut olan birçok çözümden birini kullanabilirsiniz. Bu tek başına bir framework’ün yerleşik veri alma mekanizmasını kullanmak kadar verimli olmasa da, veri getirme mantığını özel bir Hook'a taşımak, daha sonra verimli bir veri alma stratejisi benimsemeyi kolaylaştıracaktır.
Genel olarak, Efekt yazmaya başvurmak zorunda kaldığınızda, yukarıdaki useData gibi daha açıklayıcı ve amaca yönelik oluşturulmuş bir API ile özel bir Hook'a bir fonksiyon parçasını ne zaman dışarı çıkarabileceğinize dikkat edin. Bileşenlerinizde ne kadar az saf useEffect çağrısı varsa, uygulamanızın bakımını o kadar kolay bulacaksınız.

Özet

  • Render sırasında bir şeyi hesaplayabiliyorsanız, bir Efekte ihtiyacınız yoktur.
  • Pahalı hesaplamaları önbelleğe almak için useEffect yerine useMemo ekleyin.
  • Tüm bileşen ağacının durumunu sıfırlamak için ona farklı bir key iletin.
  • Bir prop değişikliğine yanıt olarak belirli bir state’i sıfırlamak için, onu oluşturma sırasında ayarlayın.
  • Bir komponent görüntülendiği için çalışması gereken kod Effects'te, geri kalanı eventlerde olmalıdır.
  • Birkaç komponentin state’ ini güncellemeniz gerekiyorsa, bunu tek bir event sırasında yapmanız daha iyi olur.
  • State değişkenlerini farklı koponentlerde senkronize etmeye çalıştığınızda, state’i üst komponente almayı düşünün.
  • Effects ile veri getirebilirsiniz, ancak race condition’ larından kaçınmak için temizleme fonksiyonu eklemeniz gerekir.
 
 
Video preview
İçeriklerinin çevirisidir.