useDeferredValue
useDeferredValue
adalah React Hook yang memungkinkan Anda menangguhkan pembaruan bagian dari UI.
const deferredValue = useDeferredValue(value)
Referensi
useDeferredValue(value, initialValue?)
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.
Parameter
value
: Nilai yang ingin Anda tangguhkan. Nilai ini dapat memiliki tipe apa saja.- Canary only opsional
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 ofvalue
that it can render instead.
Kembalian
currentValue
: During the initial render, the returned deferred value will be the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value).
Catatan penting
-
Ketika pembaruan berada di dalam Transition,
useDeferredValue
selalu mengembalikanvalue
yang baru dan tidak memunculkan render yang ditunda, karena pembaruan telah ditunda. -
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 keuseDeferredValue
, objek tersebut akan berbeda di setiap perenderan, menyebabkan render ulang latar belakang yang tidak perlu. -
Ketika
useDeferredValue
menerima nilai yang berbeda (dibandingkan denganObject.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 padavalue
, 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.
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
Anda dapat menganggapnya terjadi dalam dua langkah:
-
Pertama, React me-render ulang dengan
query
("ab"
baru) tetapi dengandeferredQuery
lama (masih"a"
). NilaideferredQuery
, yang Anda berikan ke daftar hasil, adalah ditangguhkan: itu “tertinggal” dari nilaiquery
. -
Di latar belakang, React mencoba me-render ulang dengan baik
query
dandeferredQuery
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.
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} /> </> ); }
Pendalaman
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.