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
}
Üzerinde 'current' yazan bir ok, üzerinde 'ref' yazan bir torbanın içine sokulmuş durumda.

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:

refsstate
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

useRef nasıl çalışır?

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:

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. React ref.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 istisna if (!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>
      }
    </>
  );
}