useDeferredValue

useDeferredValue adalah React Hook yang memungkinkan Anda menangguhkan pembaruan bagian dari UI.

const deferredValue = useDeferredValue(value)

Referensi

useDeferredValue(value)

Panggil fungsi useDeferredValue di tingkat atas komponen Anda untuk mendapatkan versi yang ditangguhkan dari nilai tersebut.

import { useState, useDeferredValue } from 'react';

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

Lihat contoh lainnya di bawah ini.

Parameters

  • value: Nilai yang ingin Anda tangguhkan. Nilai ini dapat memiliki tipe apa saja.

Returns

Selama render awal, nilai tangguhan yang dikembalikan akan sama dengan nilai yang Anda berikan. Selama pembaruan, React pertama-tama akan mencoba render ulang dengan nilai lama (sehingga akan mengembalikan nilai lama), dan kemudian mencoba render ulang lainnya di latar belakang dengan nilai baru (sehingga akan mengembalikan nilai yang diperbarui).

Caveats

  • Nilai yang Anda oper ke useDeferredValue harus berupa nilai primitif (seperti string dan angka) atau objek yang dibuat di luar rendering. Jika Anda membuat objek baru selama perenderan dan langsung mengopernya ke useDeferredValue, objek tersebut akan berbeda di setiap perenderan, menyebabkan render ulang latar belakang yang tidak perlu.

  • Ketika useDeferredValue menerima nilai yang berbeda (dibandingkan dengan Object.is), di selain render saat ini (ketika masih menggunakan nilai sebelumnya), ia menjadwalkan render ulang di latar belakang dengan nilai baru. Render ulang latar belakang dapat diinterupsi: jika ada pembaruan lain pada value, React akan memulai lagi render ulang latar belakang dari awal. Misalnya, jika pengguna mengetik input lebih cepat daripada bagan yang menerima nilai yang ditangguhkan dapat render ulang, bagan hanya akan render ulang setelah pengguna berhenti mengetik.

  • useDeferredValue terintegrasi dengan <Suspense>. Jika update latar belakang yang disebabkan oleh nilai baru menangguhkan UI, pengguna tidak akan melihat fallback. Mereka akan melihat nilai ditangguhkan yang lama hingga data dimuat.

  • useDeferredValue tidak dengan sendirinya mencegah permintaan jaringan tambahan.

  • Tidak ada penundaan tetap yang disebabkan oleh useDeferredValue itu sendiri. Segera setelah React menyelesaikan render ulang asli, React akan segera mulai mengerjakan render ulang latar belakang dengan nilai baru yang ditangguhkan. Pembaruan apa pun yang disebabkan oleh peristiwa (seperti mengetik) akan mengganggu render ulang latar belakang dan mendapatkan prioritas di atasnya.

  • Render ulang latar belakang yang disebabkan oleh useDeferredValue tidak mengaktifkan Efek hingga diterapkan ke layar. Jika render ulang latar belakang ditangguhkan, Efeknya akan berjalan setelah data dimuat dan pembaruan UI.


Penggunaan

Menampilkan konten basi saat konten segar sedang dimuat

Panggil fungsi useDeferredValue di tingkat atas komponen Anda untuk menangguhkan pembaruan beberapa bagian dari UI Anda.

import { useState, useDeferredValue } from 'react';

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

Selama render awal, nilai yang ditangguhkan akan sama dengan nilai yang Anda berikan.

Selama pembaruan, nilai yang ditangguhkan akan “tertinggal” dari nilai terbaru. Secara khusus, React pertama-tama akan render ulang tanpa memperbarui nilai yang ditangguhkan, dan kemudian mencoba render ulang dengan nilai yang baru diterima di latar belakang.

Mari telusuri contoh untuk melihat kapan ini berguna.

Catatan

Contoh ini menganggap Anda menggunakan salah satu sumber data yang menggunakan Suspense:

  • Pengambilan data yang menggunakan Suspense dengan framework seperti Relay dan Next.js
  • Kode komponen pemuatan lambat dengan lazy
  • Membaca nilai sebuah Promise dengan use

Pelajari lebih lanjut tentang Suspense dan batasannya.

Dalam contoh ini, komponen SearchResults ditangguhkan saat mengambil hasil penelusuran. Coba ketik "a", tunggu hasilnya, lalu edit menjadi "ab". Hasil untuk "a" diganti dengan fallback pemuatan.

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

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Cari album:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Memuat...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Pola UI alternatif yang umum adalah menangguhkan pembaruan daftar hasil dan terus menampilkan hasil sebelumnya hingga hasil baru siap. Panggil useDeferredValue untuk meneruskan versi kueri yang ditangguhkan:

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

query akan segera diperbarui, sehingga input akan menampilkan nilai baru. Namun, deferredQuery akan mempertahankan nilai sebelumnya sampai data telah dimuat, sehingga SearchResults akan menampilkan hasil lama untuk beberapa saat.

Masukkan "a" pada contoh di bawah, tunggu hasil dimuat, lalu edit input menjadi "ab". Perhatikan bagaimana alih-alih cadangan Suspense, Anda sekarang melihat daftar hasil basi hingga hasil baru dimuat:

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>
        Cari album:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Memuat...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Pendalaman

Bagaimana menangguhkan nilai bekerja dibalik layar?

Anda dapat menganggapnya terjadi dalam dua langkah:

  1. Pertama, React me-render ulang dengan query ("ab" baru) tetapi dengan deferredQuery lama (masih "a"). Nilai deferredQuery, yang Anda berikan ke daftar hasil, adalah ditangguhkan: itu “tertinggal” dari nilai query.

  2. Di latar belakang, React mencoba me-render ulang dengan baik query dan deferredQuery diperbarui ke "ab". Jika render ulang ini selesai, React akan menampilkannya di layar. Namun, jika ditangguhkan (hasil untuk "ab" belum dimuat), React akan mengabaikan upaya rendering ini, dan mencoba lagi render ulang ini setelah data dimuat. Pengguna akan terus melihat nilai yang ditangguhkan hingga data siap.

Render “latar belakang” yang ditangguhkan dapat diinterupsi. Misalnya, jika Anda mengetik input lagi, React akan mengabaikannya dan memulai kembali dengan nilai baru. React akan selalu menggunakan nilai terbaru yang diberikan.

Perhatikan bahwa masih ada permintaan jaringan per setiap penekanan tombol. Apa yang ditangguhkan di sini adalah menampilkan hasil (sampai siap), bukan permintaan jaringan itu sendiri. Bahkan jika pengguna terus mengetik, respons untuk setiap ketukan tombol akan di-cache, sehingga menekan Backspace akan instan dan tidak mengambil lagi.


Menandakan bahwa konten tersebut sudah basi

Pada contoh di atas, tidak ada indikasi bahwa daftar hasil kueri terbaru masih dimuat. Ini dapat membingungkan pengguna jika hasil baru membutuhkan waktu untuk dimuat. Untuk membuatnya lebih jelas bagi pengguna bahwa daftar hasil tidak cocok dengan kueri terbaru, Anda dapat menambahkan indikasi visual saat daftar hasil basi ditampilkan:

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

Dengan perubahan ini, segera setelah Anda mulai mengetik, daftar hasil basi menjadi sedikit redup hingga daftar hasil baru dimuat. Anda juga bisa menambahkan transisi CSS untuk menangguhkan peredupan agar terasa bertahap, seperti pada contoh di bawah ini:

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>
        Cari album:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Memuat...</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>
    </>
  );
}


Menangguhkan rendering ulang untuk bagian UI

Anda juga dapat menerapkan useDeferredValue sebagai pengoptimalan kinerja. Ini berguna ketika bagian dari UI Anda lambat untuk di-render ulang, tidak ada cara mudah untuk mengoptimalkannya, dan Anda ingin mencegahnya memblokir UI lainnya.

Bayangkan Anda memiliki bidang teks dan komponen (seperti bagan atau daftar panjang) yang di-render ulang pada setiap penekanan tombol:

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

Pertama, optimalkan SlowList untuk melewati rendering ulang saat propertinya sama. Untuk melakukannya, bungkus dalam memo:

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

Namun, ini hanya membantu jika props SlowList sama dengan selama render sebelumnya. Masalah yang Anda hadapi sekarang adalah lambat saat berbeda, dan saat Anda benar-benar perlu menampilkan keluaran visual yang berbeda.

Konkritnya, masalah kinerja utama adalah setiap kali Anda mengetik ke input, SlowList menerima properti baru, dan me-render ulang seluruh pohonnya membuat pengetikan terasa tersendat. Dalam hal ini, useDeferredValue memungkinkan Anda memprioritaskan pembaruan input (yang harus cepat) daripada memperbarui daftar hasil (yang diizinkan lebih lambat):

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

Ini tidak mempercepat rendering ulang SlowList. Namun, ini memberi tahu React bahwa me-render ulang daftar dapat diturunkan prioritasnya sehingga tidak memblokir penekanan tombol. Daftar akan “tertinggal” input dan kemudian “mengejar”. Seperti sebelumnya, React akan berusaha memperbarui daftar sesegera mungkin, tetapi tidak akan menghalangi pengguna untuk mengetik.

Perbedaan antara useDeferredValue dan rendering ulang yang tidak dioptimalkan

Contoh 1 dari 2:
Render ulang daftar yang ditangguhkan

Dalam contoh ini, setiap item dalam komponen SlowList diperlambat secara artifisial sehingga Anda dapat melihat bagaimana useDeferredValue membuat input tetap responsif. Ketik input dan perhatikan bahwa mengetik terasa cepat sementara daftar “tertinggal”.

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} />
    </>
  );
}

Sandungan

Optimalisasi ini membutuhkan SlowList untuk dibungkus dengan memo. Hal ini karena setiap kali text berubah, React harus dapat me-render ulang komponen induk dengan cepat. Selama pe-render-an ulang itu, deferredText masih memiliki nilai sebelumnya, jadi SlowList dapat melewati pe-render-an ulang (propnya tidak berubah). Tanpa memo, itu harus di-render ulang, mengalahkan poin pengoptimalan.

Pendalaman

Bagaimana penangguhan nilai berbeda dari debouncing dan throttling?

Ada dua teknik pengoptimalan umum yang mungkin pernah Anda gunakan sebelumnya dalam skenario ini:

  • Debouncing berarti Anda akan menunggu pengguna berhenti mengetik (mis. sesaat) sebelum memperbarui daftar.
  • Throttling berarti Anda memperbarui daftar sesekali (mis. paling banyak sekali dalam satu detik).

Meskipun teknik ini membantu dalam beberapa kasus, useDeferredValue lebih cocok untuk mengoptimalkan rendering karena sangat terintegrasi dengan React itu sendiri dan beradaptasi dengan perangkat pengguna.

Tidak seperti debouncing atau throttling, ini tidak memerlukan pemilihan penundaan tetap. Jika perangkat pengguna cepat (misalnya laptop yang kuat), rendering ulang yang ditangguhkan akan segera terjadi dan tidak akan terlihat. Jika perangkat pengguna lambat, daftar akan “tertinggal” input secara proporsional dengan seberapa lambat perangkat tersebut.

Selain itu, tidak seperti debouncing atau throttling, rendering ulang yang ditangguhkan yang dilakukan oleh useDeferredValue dapat diinterupsi secara default. Ini berarti bahwa jika React sedang me-render ulang daftar besar, tetapi pengguna membuat keystroke lain, React akan mengabaikan render ulang itu, menangani keystroke, dan kemudian mulai me-render di latar belakang lagi. Sebaliknya, debouncing dan throttling masih menghasilkan pengalaman tersendat karena keduanya memblokir: keduanya hanya menangguhkan momen saat me-render memblokir keystroke.

Jika pekerjaan yang Anda optimalkan tidak terjadi selama rendering, debouncing dan throttling tetap berguna. Misalnya, mereka dapat membiarkan Anda memecat lebih sedikit permintaan jaringan. Anda juga dapat menggunakan teknik ini bersama-sama.