useDeferredValue

useDeferredValue, kullanıcı arayüzünün belirli bir bölümünün güncellenmesini ertelemenizi sağlayan React Hook’udur.

const deferredValue = useDeferredValue(value)

Referans

useDeferredValue(value, initialValue?)

Belirli bir değerin ertelenmiş (deferred) versiyonunu almak için bileşeninizin en üst kapsamında useDeferredValue’ı çağırın.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

Daha fazla örnek için aşağıya bakınız.

Parametreler

  • value: Ertelemek istediğiniz değerdir. Herhangi bir türden olabilir.
  • Canary only optional initialValue: A value to use during the initial render of a component. If this option is omitted, useDeferredValue will not defer during the initial render, because there’s no previous version of value that it can render instead.

Dönüş değeri

  • currentValue: İlk render esnasında, döndürülen ertelenmiş değer ile sağladığınız değer aynı olacaktır. Güncellemeler esnasında, React önce eski değerle yeniden render etmeyi dener (bu yüzden eski değeri döndürür) ve ardından arka planda yeni değer ile birlikte yeni bir render başlatır (bu yüzden güncellenmiş değeri döndürür).

Test Ortamı (Canary)

In the latest React Canary versions, useDeferredValue returns the initialValue on initial render, and schedules a re-render in the background with the value returned.

Dikkat edilmesi gerekenler

  • Bir güncelleme Transition içinde olduğunda, güncelleme zaten ertelendiği için, useDeferredValue daima yeni value değerini döner ve ertelenmiş bir render oluşturmaz.

  • useDeferredValue’ya geçtiğiniz değerler, ilkel değer (örn. string ya da number) veya render dışında oluşturulan nesneler olmalıdır. Render esnasında yeni bir nesne oluşturur ve bunu direkt useDeferredValue’ya iletirseniz, her render’da farklı olur. Bu da gereksiz arka plan render’larına neden olacaktır.

  • useDeferredValue farklı bir değer aldığında (Object.is ile karşılaştırılarak karar verilir), mevcut render’a (hala önceki değeri kullandığı) ek olarak, arkaplanda yeni değerle render planlar. Arka planda gerçekleşen bu render kesintiye uğrayabilir: value için başka bir güncelleme olursa React render’a sıfırdan başlar. Örneğin, kullanıcı formdaki girdiye ertelenmiş değeri alan grafiğin yeniden render’lanmasına fırsat vermeyecek kadar hızlı yazıyorsa, grafik yalnızca kullanıcı yazmayı bıraktıktan sonra yeniden render edilir.

  • useDeferredValue, <Suspense> ile entegredir. Yeni bir değerin sebep olduğu arkaplan güncellemesi UI’yı askıya aldığında, kullanıcı fallback’i görmez. Veriler yüklenene kadar eski değeri görürler.

  • useDeferredValue, tek başına ekstra ağ isteklerini engellemez.

  • useDeferredValue Hook’unun sebep olduğu sabit bir gecikme yoktur. React orjinal render işlemini bitirir bitirmez, hemen yeni değerle arkaplanda render işlemine başlar. Olayların sebep olduğu güncellemeler (örn. yazma), arkaplanda yeniden render işlemini kesintiye uğratarak önceliklendirilirler.

  • useDeferredValue’dan kaynaklanan arkaplan render işlemi, ekrana işlenene kadar Efektleri tetiklemez. Arka planda gerçekleşen render askıya alınırsa, Efektler veriler yüklendikten ve kullanıcı arayüzü güncellendikten sonra çalıştırılır.


Kullanım

Taze içerik yüklenirken eski içeriğin gösterilmesi

Kullanıcı arayüzünüzün bazı bölümlerinin güncellenmesini ertelemek için bileşeninizin en üst kapsamında useDeferredValue çağırın.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

İlk render esnasında, ertelenmiş değer ile sağladığınız değer aynı olacaktır.

Güncellemeler esnasında, ertelenmiş değer en son değerin “gerisinde” kalır. React ilk seferde ertelenmiş değeri güncellemeden render eder, ardından yeni değerle arka planda yeniden render işlemi planlar.

Bunun ne zaman faydalı olabileceğini görmek için bir örnek üzerinden ilerleyelim.

Not

Bu örnekte Suspense etkinleştirilmiş veri kaynaklarından birini kullandığınız varsayılmaktadır:

  • Relay ve Next.js gibi Suspense etkinleştirilmiş çatılar vasıtasıyla veri çekilmesi
  • lazy ile bileşen kodunun lazy yüklenmesi
  • use ile bir Promise’in değerini okuma.

Suspense ve sınırlamaları hakkında daha fazla bilgi edinin.

Bu örnekte, SearchResults bileşeni arama sonuçları çekilirken askıya alınır. "a" yazmayı deneyin. Ardından sonuçları bekleyip "ab" olarak düzenleyin. "a"’nın sonuçları, yükleme fallback’iyle değiştirilir.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Albüm ara:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Yükleniyor...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Buna yaygın bir alternatif olan arayüz modeli, sonuç listesinin güncellenmesini ertelemek ve yeni sonuçlar hazır olana kadar öncekini göstermeye devam etmektir. Sorgunun ertelenmiş sürümünü aşağıya aktarmak için useDeferredValue’yu çağırın:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Albüm ara:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Yükleniyor...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

query hemen güncelleneceği için girdi yeni değeri gösterir. Ancak, veri yüklenene kadar deferredQuery önceki değerini korur ve SearchResults bir süre boyunca eski sonuçları göstermeye devam eder.

Aşağıdaki örnekte "a" yazın, sonuçların yüklenmesini bekleyin ve ardından girdiyi "ab" olarak düzenleyin. Yeni sonuçlar yüklenene kadar Suspense fallback’in yerine eski sonuç listesinin görüntülendiğine dikkat edin:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Albüm ara:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Yükleniyor...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Derinlemesine İnceleme

Değer ertelemek arkaplanda nasıl çalışır?

Bunu iki adımda gerçekleşen bir süreç olarak düşünebilirsiniz:

  1. İlk olarak, React yeni query ("ab") ancak eski deferredQuery (hala "a") ile yeniden render gerçekleştirir. Sonuç listesine ilettiğiniz deferredQuery değeri ertelenmiştir: query değerinin “gerisindedir”.

  2. Arka planda, React query ve deferredQuery’nin her ikisini de "ab" olarak yeniden render etmeye çalışır. Bu render tamamlandığında, React ekranda gösterir. Ancak askıya alınırsa ("ab"’nin sonuçları henüz yüklenmediyse), React render’dan vazgeçer ve veriler yüklendikten sonra tekrar render etmeyi dener. Kullanıcı, veriler hazır olana kadar eski ertelenmiş değeri görmeye devam eder.

Ertelenmiş “arkaplan” render işlemi kesintiye uğrayabilir. Örneğin, girdiyi tekrar yazdığınızda React render’dan vazgeçer ve yeni değerle yeniden render etmeyi dener. Daima en son sağlanan değer kullanılır.

Klavyeye her bastığınızda hala ağ isteği atılacağını unutmayın. Burada ertelenen şey; ağ isteklerinin ertelenmesi değil, sonuçların görüntülenmesidir (hazır olana kadar). Kullanıcı yazmaya devam etse bile tuşa her bastığınızda yanıt (response) önbelleğe alınır. Bu nedenle Backspace tuşuna basmak anlık olarak gerçekleşir ve veri tekrar çekilmez.


İçeriğin eski olduğunu belirtme

Yukarıdaki örnekte, en son sorgu için sonuç listesinin hala yüklenmekte olduğuna dair herhangi bir gösterge yoktur. Yeni sonuçların yüklenmesi zaman alıyorsa, kullanıcı için kafa karıştırıcı durumlar oluşabilir. Sonuç listesinin en son sorgu ile güncellenmediğini kullanıcıya daha belirgin hale getirmek için, eski sonuç listesi görüntülenirken görsel bir gösterge ekleyebilirsiniz:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>

Bu değişiklikle birlikte, yazmaya başladığınız anda yeni sonuç listesi yüklenene kadar eski liste karartılır. Karartmayı daha yumuşak hissettirmek için aşağıdaki örnekte olduğu gibi CSS geçiş efekti ekleyebilirsiniz:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Albüm ara:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Yükleniyor...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


Kullanıcı arayüzünün bir kısmı için yeniden render’ı erteleme

useDeferredValue’ı performans optimizasyonu olarak da uygulayabilirsiniz. Kullanıcı arayüzünüzün bir bölümünün yeniden işlenmesi yavaş olduğunda, optimize etmenin kolay bir yolu olmadığında ve kullanıcı arayüzünün geri kalanını engellemesini önlemek istediğinizde kullanışlıdır.

Bir metin girdinizin olduğunu ve her tuşa bastığınızda yeniden render edilen bileşeninizin (grafik ya da uzun bir liste gibi) olduğunu düşünün:

function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}

İlk olarak, prop’ları aynı olduğunda SlowList’in yeniden render edilmesini atlayacak şekilde optimize edin. Bunu yapmak için, memo’ya sarın:

const SlowList = memo(function SlowList({ text }) {
// ...
});

Ancak, bu yalnızca SlowList prop’ları önceki render’dakiyle aynı ise yardımcı olur. Şu an karşılaştığınız sorun, prop’lar farklı olduklarında ve farklı çıktı göstermeniz gerektiğinde yavaş olmasıdır.

Temel performans sorunu, girdiye her yazdığınızda SlowList’in yeni prop alması ve tüm ağacın yeniden render olmasından dolayı yazmayı hantal hissettirmesidir. Bu durumda useDeferredValue, girdinin güncellenmesini (hızlı olması gerekir) sonuç listesinin güncellenmesine (daha yavaş olmasına izin verilir) göre önceliklendirmenizi sağlar:

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}

Bu değişiklik, SlowList bileşeninin yeniden render edilmesini hızlandırmaz. Fakat, React’e listenin yeniden render edilme önceliğinin düşürülebileceğini ve böylece tuşa basışları engellemeyeceğini belirtir. Liste girdinin “gerisinde kalır” ve sonra “yakalar”. Daha önce olduğu gibi, React listeyi mümkün olan en kısa sürede güncellemeye çalışır ancak bunu yaparken kullanıcının yazmaya devam etmesini engellemez.

useDeferredValue ve optimize edilmemiş render arasındaki fark

Örnek 1 / 2:
Listenin yeniden render’ının ertelenmesi

Bu örnekte, SlowList bileşenindeki her bir öğe yapay olarak yavaşlatılmıştır. Böylece useDeferredValue’nun etkisini rahatça görebilirsiniz. Girdiye yazın ve liste “geride kalırken” yazış hissinin ne kadar akıcı hissettirdiğine dikkat edin.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Tuzak

Bu optimizasyon, SlowList bileşeninin memo’ya sarmalanmasını gerektirir. Bunun nedeni, text her değiştiğinde React’in üst bileşeni hızlıca yeniden render etmesi gerektiğidir. Yeniden render işlemi sırasında, deferredText’in hala önceki değere sahiptir ve bu nedenle SlowList render işlemini atlayabilir (prop’ları aynıdır). memo olmasaydı, yeniden render etmek zorunda kalır ve optimizasyonun amacından sapardı.

Derinlemesine İnceleme

Bir değeri ertelemenin debouncing ve throttling’den farkı nedir?

Bu senaryoda daha önce kullanmış olabileceğiniz iki yaygın optimizasyon tekniği vardır:

  • Debouncing, listeyi güncellemeden önce kullanıcının yazmayı bırakmasını (örneğin bir saniyeliğine) bekleyeceğiniz anlamına gelir.
  • Throttling, listeyi arada bir (örneğin saniyede en fazla bir kere) güncelleyeceğiniz anlamına gelir.

Bu teknikler bazı durumlarda yardımcı olsa da, useDeferredValue React ile entegre olduğu ve kullanıcının cihazına uyum sağladığı için render’ı optimize etmeye daha uygundur.

Debouncing ve throttling’in aksine herhangi bir sabit gecikme belirlenmesi gerekmez. Kullanıcının cihazı hızlıysa (örneğin güçlü bir dizüstü bilgisayara sahipse), ertelenmiş yeniden render anında gerçekleşir ve fark edilmez. Kullanıcının cihazı yavaşsa, liste cihazın yavaşlığıyla orantılı olarak girdinin “gerisinde” kalır.

Ayrıca, debouncing ve throttling’in aksine, useDeferredValue tarafından yapılan ertelenmiş render işlemi kesintiye uğratılabilir. Büyük bir liste yeniden render edilirken kullanıcı başka tuşa basarsa, React mevcut render’ı iptal eder ve başka bir yeniden render tetikler. Buna karşın, debouncing ve throttling kötü bir deneyim sağlar çünkü yalnızca basılan tuşun render’ı blokladığı anı ertelerler.

Optimize ettiğiniz iş render esnasında gerçekleşmiyorsa, debouncing ve throttling hala yararlıdır. Örneğin, daha az ağ isteği göndermenizi sağlayabilirler. Bu teknikleri birlikte de kullanabilirsiniz.