Ref ile Değerlere Referans Verme
Bir bileşenin “hatırlamasını” istediğiniz bilgi varsa, ancak bu bilginin yeni render’lar tetiklemesini istemiyorsanız, bir ref kullanabilirsiniz.
Bunları öğreneceksiniz
- Bir bileşene ref nasıl eklenir
- Bir ref’in değerini nasıl güncelleyebilirsiniz
- Ref’lerin state’ten farkı nedir
- Ref’leri güvenli bir şekilde nasıl kullanabilirsiniz
Bir bileşene ref eklemek
Bileşeninize bir ref eklemek için, useRef
Hook’unu React’ten içe aktarın:
import { useRef } from 'react';
Bileşeninizin içinde useRef
Hook’unu çağırın ve yalnızca bir argüman olarak referans vermek istediğiniz başlangıç değerini geçirin. Örneğin, değeri 0
olan bir ref:
const ref = useRef(0);
useRef
size aşağıdaki gibi bir nesne döndürür:
{
current: 0 // useRef'a geçirdiğiniz değer
}
Rachel Lee Nabors tarafından görselleştirilmiştir.
Bu ref’in geçerli değerine ref.current
özelliği üzerinden erişebilirsiniz. Bu değer kasıtlı olarak değiştirilebilir, yani hem okunabilir hem de yazılabilir. Bu bileşeninizin React tarafından takip edilmediği anlamına gelir. (Bu, onu React’in tek yönlü veri akışından kaçmanızı sağlayan bir “kaçış noktası” yapar, buna aşağıda daha fazla değineceğiz!)
Her tıklamada ref.current
’i artıracak bir düğme:
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert(ref.current + ' kez tıkladınız!'); } return ( <button onClick={handleClick}> Tıkla! </button> ); }
Ref bir sayıyı işaret ediyor fakat state gibi herhangi bir şeye işaret edebilirsiniz: bir string, bir nesne veya hatta bir fonksiyon. State’in aksine, ref, current
özelliği olan basit bir JavaScript nesnesidir. Bu özelliği okuyabilir ve değiştirebilirsiniz.
Dikkat edin ref her arttığında bileşen yeniden render edilmez. State gibi, ref’ler de React tarafından yeniden render’lar arasında saklanır. Ancak, state’i değiştirmek bileşeni yeniden render eder. Bir ref’i değiştirmek etmez!
Örnek: bir kronometre oluşturma
Ref’ler ve state’i tek bir bileşenin içinde birlikte kullanabilirsiniz. Örnegin, kullanıcının bir düğmeye basarak başlatabileceği veya durdurabileceği bir kronometre yapalım. Kullanıcının “Başlat” düğmesine bastığı zamandan beri geçen süreyi göstermek için, “Başlat” düğmesinin ne zaman basıldığını ve şu anki zamanı takip etmeniz gerekir. Bu bilgi render etmek için kullanıldığından, onu state’te tutacaksınız:
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
Kullanıcı “Başlat” düğmesine bastığında, zamanı her 10 milisaniyede bir güncellemek için setInterval
kullanacaksınız:
import { useState } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); function handleStart() { // Saymaya başla setStartTime(Date.now()); setNow(Date.now()); setInterval(() => { // Şuanki zamanı her 10ms'de bir güncelle. setNow(Date.now()); }, 10); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Geçen zaman: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Başlat </button> </> ); }
“Durdur” düğmesine basıldığında now
state değişkeninin güncellenmesini durdurmak için varolan intervali iptal etmemiz gerekiyor. Bunu yapmak için clearInterval
çağırmamız gerekiyor, ancak kullanıcı Başlat’a bastığında setInterval
çağrısından dönen interval ID’sini vermeniz gerekir. Bu interval ID’yi bir yerde saklamanız gerekir. Interval ID herhangi bir render işleminde kullanılmadığından, onu bir ref’te saklayabilirsiniz:
import { useState, useRef } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Geçen zaman: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Başlat </button> <button onClick={handleStop}> Durdur </button> </> ); }
Ne zaman bir bilgi render etmek için kullanılırsa, onu state’te tutun. Bir bilgi parçası yalnızca olay işleyicileri için gerekiyorsa ve değiştirmek bir yeniden render gerektirmiyorsa, bir ref kullanmak daha verimli olabilir.
Ref ve state arasındaki farklar
Muhtemelen ref’lerin state’ten daha “esnek” olduğunu düşünüyorsunuz. Örneğin, ref’leri state ayarlama fonksiyonu olmadan değiştirebilirsiniz. Ama çoğu zaman state kullanmak isteyeceksiniz. Ref’ler çoğunlukla ihtiyacınız olmayan bir “kaçış noktası”dır. State ve ref’ler arasındaki farkları görelim:
refs | state |
---|---|
useRef(initialValue) { current: initialValue } döndürür. | useState(initialValue) state değişkeninin şuanki değerini ve bir state ayarlama fonksiyonu ([value, setValue] ) döndürür. |
Değiştirildiğinde yeniden render tetiklenmez. | Değiştirildiğinde yeniden render tetiklenir. |
Değiştirilebilir; current değerini render işlemi dışında da değiştirilebilir ve güncelleyebilirsiniz. | Değiştirilemez; yeniden render tetiklemek için state değiştirme fonksiyonunu kullanmalısınız. |
Render işlemi sırasında current değerini okumamalısınız (veya yazmamalısınız). | State’i her zaman okuyabilirsiniz. Ancak, her render’ın değişmeyen kendi anlık görüntüsü vardır. |
State kullanılarak oluşturulmuş bir sayaç buton’u:
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> {count} kez tıkladınız. </button> ); }
count
değeri ekranda görüntülendiği için onu state’te tutmak mantıklıdır. Sayacın değeri setCount()
ile ayarlandığında, React bileşeni yeniden render eder ve ekran yeni sayıyı yansıtır.
Eğer bunu ref kullanarak yapmaya çalışırsanız, React bileşeni yeniden render etmez ve sayıyı ekranda göremezsiniz! Bu düğmeye tıkladığınızda sayı değişmez:
import { useRef } from 'react'; export default function Counter() { let countRef = useRef(0); function handleClick() { // Bu bileşeni yeniden render etmez! countRef.current = countRef.current + 1; } return ( <button onClick={handleClick}> {countRef.current} kez tıkladınız. </button> ); }
Bu yüzden render işlemi sırasında ref.current
değerini okumak güvenilmez kod yazmanıza neden olur. Eğer bunu yapmanız gerekiyorsa, bunun yerine state kullanın.
Derinlemesine İnceleme
useState
ve useRef
React tarafından sağlansa da, useRef
prensipte useState
üzerinde oluşturulabilir. React içinde useRef
’in aşağıdaki gibi oluşturulduğunu hayal edebilirsiniz:
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
İlk render sırasında, useRef
{ current: initialValue }
döndürür. Bu nesne React tarafından saklanır, bu yüzden bir sonraki render sırasında aynı nesne döndürülür. Bu örnekte state ayarlama fonksiyonunun kullanılmadığına dikkat edin. Her zaman aynı nesneyi döndürmesi gerektiği için gereksizdir!
Pratikte yeterince yaygın olduğu için, React useRef
’i içinde sağlar. Fakat onu bir ayarlayıcı olmadan normal bir state değişkeni olarak düşünebilirsiniz. Nesne yönelimli programlamaya aşinaysanız, ref’ler size nesne değişkenlerini hatırlatabilir - fakat this.something
yerine somethingRef.current
yazarsınız.
Ref’ler ne zaman kullanılmalıdır?
Genellikle, bileşeninizin React “dışına çıkması” ve harici API’lar (genellikle bileşenin görünümünü etkilemeyen bir tarayıcı API’si olabilir) ile iletişim kurması gerektiğinde bir ref kullanırsınız. İşte bu nadir durumlara birkaç örnek:
- timeout ID’lerini saklamak
- DOM elemanlarını saklamak ve manipüle etmek. Bunu bir sonraki sayfada ele alacağız.
- JSX’i hesaplamak için gerekli olmayan diğer nesneleri saklamak.
Eğer bileşeninizin bir değeri saklaması gerekiyorsa, ancak render mantığını etkilemiyorsa, ref’leri seçin.
Ref’ler için en iyi pratikler
Bu prensipleri takip etmek bileşenlerinizi daha öngörülebilir hale getirecektir:
- Ref’lere bir kaçış noktası gibi davranın Ref’ler harici sistemler veya tarayıcı API’ları ile çalışırken yararlıdır. Uygulamanızın mantığının ve veri akışının büyük bir kısmı ref’lere bağlıysa, yaklaşımınızı yeniden düşünmelisiniz.
- Render işlemi sırasında
ref.current
’i okumayın veya yazmayın. Render işlemi sırasında bazı bilgilere ihtiyaç duyuluyorsa, bunun yerine state kullanın. Reactref.current
’in ne zaman değiştiğini bilmediği için, render işlemi sırasında okumak bileşeninizin davranışının tahmin edilmesini zorlaştırır. (Tek istisnaif (!ref.current) ref.current = new Thing()
gibi bir koddur, bu sadece ref’i ilk render sırasında bir kez ayarlar.)
React state’in kısıtlamaları ref’ler için geçerli değildir. Örneğin, state her render için anlık görüntü gibi davranır ve eşzamanlı olarak güncellenmez. Fakat bir ref’in geçerli değerini değiştirdiğinizde, hemen değişir:
ref.current = 5;
console.log(ref.current); // 5
Bunun nedeni ref’in kendisinin normal bir JavaScript nesnesi olması ve öyle davranılmasıdır.
Ayrıca bir ref ile çalışırken mutasyondan kaçınmaya gerek yoktur. Mutasyona uğrayan nesne render işlemi için kullanılmıyorsa, React ref veya içeriğiyle ne yaptığınızı umursamaz.
Ref’ler ve DOM
Bir ref’i herhangi bir değere işaret edecek şekilde ayarlayabilirsiniz. Fakat ref’lerin en yaygın kullanımı DOM elemanlarına erişmektir. Örneğin, bir input’a programatik olarak odaklanmak istiyorsanız bu kullanışlıdır. JSX’te ref
özelliğine bir ref geçtiğinizde, <div ref={myRef}>
gibi, React karşılık gelen DOM elemanını myRef.current
’e koyar. Bir eleman DOM’dan kaldırıldığı zaman, React myRef.current
değerini null
olarak günceller. Bunu Ref’ler ile DOM’u Manipüle etme bölümünde daha fazla okuyabilirsiniz.
Özet
- Ref’ler render işlemi için kullanılmayan değerleri tutmak için kullanılan bir kaçış noktasıdır. Bunlara çok fazla ihtiyacınız olmayacak.
- Bir ref,
current
adında tek bir özelliği olan basit bir JavaScript nesnesidir. Bu özelliği okuyabilir veya ayarlayabilirsiniz. - React’ten size bir ref vermesi için
useRef
Hook’unu çağırabilirsiniz. - State gibi, ref’ler de bileşenler arasında yeniden render’lar arasında bilgi tutmanızı sağlar.
- State’in aksine, ref’in
current
değerini ayarlamak yeniden render tetiklemez. - Render işlemi sırasında
ref.current
’i okumayın veya yazmayın. Bu bileşeninizi tahmin edilmesi zor hale getirir.
Problem 1 / 4: Bozuk bir sohbet inputunu düzelt
Bir mesaj yazın ve “Gönder” butonuna basın. “Gönderildi!” uyarısını görmek için üç saniye beklemeniz gerektiğini fark edeceksiniz. Bu gecikme sırasında “Geri Al” butonunu görebilirsiniz. Ona tıklayın. Bu “Geri Al” butonunun “Gönderildi!” mesajının görünmesini durdurması gerekiyor. “Geri Al” butonuna tıklandığında handleSend
sırasında kaydedilen timeout ID ile clearTimeout
çağrısı yapıyor. Ancak, “Geri Al” tıklandıktan sonra bile “Gönderildi!” mesajı hala görünüyor. Neden çalışmadığını bulun ve düzeltin.
import { useState } from 'react'; export default function Chat() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); let timeoutID = null; function handleSend() { setIsSending(true); timeoutID = setTimeout(() => { alert('Gönderildi!'); setIsSending(false); }, 3000); } function handleUndo() { setIsSending(false); clearTimeout(timeoutID); } return ( <> <input disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <button disabled={isSending} onClick={handleSend}> {isSending ? 'Gönderiliyor...' : 'Gönder'} </button> {isSending && <button onClick={handleUndo}> Geri Al </button> } </> ); }