Menu

React useDeferredValue: Biarkan Bagian Lambat Tertinggal

useDeferredValue memberimu salinan sebuah nilai yang boleh tertinggal saat render berat, sehingga input tetap responsif sementara daftar yang lambat menyusul. Pelajari cara kerjanya, nilai awal di React 19, dan perbandingannya dengan useTransition dan debounce.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

useDeferredValue memberimu salinan sebuah nilai yang boleh tertinggal. Saat nilainya berubah, React lebih dulu menggambar ulang layar dengan salinan yang lama, sehingga mengetik terasa instan, lalu merender bagian yang lambat dengan nilai baru di latar belakang.

Ketik sebuah kata dengan cepat. Input-nya tetap mengikuti dan daftarnya meredup selama masih tertinggal. Console mencatat satu baris SlowList rendering per huruf, karena SlowList mencatat log begitu React mulai merendernya, tapi daftar untuk nilai-nilai di antaranya tidak pernah sampai ke layar: setiap huruf baru menyela render latar belakang, dan React memulai ulang dengan teks terbaru. Ganti query={deferredQuery} menjadi query={query} lalu ketik lagi: sekarang setiap ketukan tombol harus menunggu seperempat detik untuk daftarnya.

Sintaksnya

const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
  • value adalah apa pun yang kamu render: string dari input, angka, object. Nilai primitif bekerja paling baik, karena object baru di setiap render selalu dihitung sebagai perubahan.
  • Nilai yang dikembalikan sebagian besar waktu sama dengan value. Tepat setelah value berubah, nilai itu masih menyimpan nilai sebelumnya selama satu render, lalu menyusul di render latar belakang.

Cara kerjanya

Setiap perubahan menghasilkan dua render:

  1. Render mendesak dengan value yang baru dan deferredValue yang lama. Input menampilkan teks baru. Komponen yang hanya menerima deferredValue mendapat props yang sama seperti sebelumnya.
  2. Render latar belakang dengan deferredValue yang baru. Inilah render yang lambat. React merendernya sepotong demi sepotong dan meninggalkannya kalau value berubah lagi sebelum selesai, lalu memulai ulang dengan nilai terbaru.

Kamu bisa melihatnya terjadi dengan mencatat kedua nilai itu:

Ketik satu huruf, misalnya a, dan Console mencetak dua baris lagi: pertama text = "a" dengan deferred = "", lalu keduanya "a". Render pertama itulah yang menjaga agar mengetik tetap cepat, tapi hanya kalau bagian yang lambat bisa melewatinya. Itulah sebabnya SlowList di contoh pertama dibungkus memo: selama render mendesak, prop query miliknya belum berubah, jadi React melewatinya. Tanpa memo, render mendesak tetap menjalankan seluruh daftar yang lambat, hanya dengan teks lama, dan kamu tidak mendapat apa-apa.

Menampilkan konten yang sudah usang

Selama render latar belakang masih berjalan, value dan nilai deferred berbeda. Bandingkan keduanya untuk memberi tahu pengguna bahwa kontennya sudah tidak terbaru, tanpa menyembunyikannya:

const isStale = query !== deferredQuery;

<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
    <SlowList query={deferredQuery} />
</div>

Ini biasanya lebih nyaman daripada spinner: hasil lama tetap terbaca dan berubah di tempat saat hasil baru siap. Ide yang sama berlaku dengan Suspense: kalau nilai deferred membuat sebuah komponen ditangguhkan selama memuat data, React tetap menampilkan konten lama alih-alih fallback sampai konten baru siap (lihat Suspense dan lazy).

Nilai awal (React 19)

Secara default, render pertama tidak punya apa pun untuk ditinggalkan, jadi nilai deferred sudah sama dengan nilai asli sejak awal. Sejak React 19, kamu bisa memberikan argumen kedua. Di render pertama, hook mengembalikan initialValue, lalu React merender lagi di latar belakang dengan nilai yang sebenarnya. Ini membuatmu bisa menampilkan sesuatu yang ringan seketika dan mengisi bagian yang berat tepat sesudahnya.

Console menampilkan dua render: pertama dengan "", lalu dengan teks lengkapnya. Hapus argumen kedua dan hanya satu render yang tercatat, langsung dengan teks lengkap.

useDeferredValue vs useTransition

Keduanya menandai pekerjaan sebagai tidak mendesak dan memakai mesin rendering yang sama. Perbedaannya ada di tempat kamu memakainya:

  • useTransition membungkus pembaruannya. Kamu memanggil startTransition(() => setQuery(text)), jadi kamu butuh setter-nya. Kamu mendapat isPending secara cuma-cuma.
  • useDeferredValue membungkus nilainya. Kamu memanggilnya di tempat nilai itu dipakai, jadi hook ini berfungsi saat nilainya datang sebagai prop atau berasal dari hook yang tidak kamu kendalikan. Kamu menghitung "pending" sendiri dengan value !== deferredValue.
// You own the state: either works, a transition is direct
startTransition(() => setQuery(nextQuery));

// You only receive the value
function SearchResults({ query }) {
    const deferredQuery = useDeferredValue(query);
    return <SlowList query={deferredQuery} />;
}

useDeferredValue vs debounce dan throttle

Debounce menunggu waktu tetap setelah perubahan terakhir; throttle memperbarui paling banyak sekali per interval. Keduanya memilih jeda sejak awal, jadi di laptop yang cepat keduanya membuat halaman lebih lambat dari yang perlu, dan di ponsel yang lambat jedanya mungkin masih terlalu pendek.

useDeferredValue tidak punya jeda. Render latar belakang dimulai seketika, jadi di perangkat yang cepat daftarnya diperbarui hampir seketika, dan di perangkat yang lambat React menjaga input tetap responsif dan merender daftarnya sesering yang bisa dilakukannya. Pekerjaan latar belakang juga bisa disela, sesuatu yang tidak bisa dilakukan render dengan debounce: begitu render yang di-debounce dimulai, render itu memblokir sampai selesai.

Yang tidak dilakukannya adalah mengurangi request jaringan. Kalau setiap nilai baru memulai fetch, nilai deferred tetap berubah di setiap render yang selesai, jadi lakukan debounce pada request-nya sendiri saat jumlah request itu penting.

Kesalahan umum

  • Lupa memo. Seperti yang ditunjukkan di atas, komponen yang lambat harus bisa melewati render mendesak. Kalau komponen itu tidak dibungkus memo, atau salah satu prop lainnya adalah object baru di setiap render, komponen itu dirender dua kali per ketukan tombol, bukan sekali.
  • Memberikan object baru. useDeferredValue({ query }) membuat object baru di setiap render, jadi nilai deferred selalu terlihat berubah dan React memulai render latar belakang tambahan setiap kali. Tunda nilai primitifnya (useDeferredValue(query)) lalu susun object dari nilai itu.
  • Berharap request berkurang. Komponen yang melakukan fetch berdasarkan nilai deferred tetap melakukan fetch untuk setiap nilai yang selesai dirender.

Kapan memakainya

Pakai useDeferredValue saat satu input menggerakkan bagian halaman yang lambat dirender, bagian lambat itu bisa di-memoize, dan kamu tidak keberatan menampilkan hasil yang sedikit usang sejenak: hasil pencarian, grafik yang digambar ulang dari sebuah filter, pratinjau teks berformat. Kalau bagian yang lambat itu lambat karena alasan yang bisa kamu perbaiki, seperti merender 5.000 baris padahal yang terlihat hanya 50, perbaiki itu dulu. Untuk versi pola ini saat kamu memegang setter-nya, lihat useTransition.

Pertanyaan yang sering diajukan

Apa fungsi useDeferredValue?

useDeferredValue mengembalikan versi sebuah nilai yang boleh tertinggal dari nilai aslinya. Saat nilainya berubah, React lebih dulu merender dengan nilai deferred yang lama, lalu merender lagi di latar belakang dengan nilai yang baru, dan render latar belakang itu bisa disela.

Mengapa useDeferredValue tidak membuat daftar saya lebih cepat?

Komponen yang lambat harus dibungkus memo dan menerima nilai deferred. Kalau tidak, render mendesak tetap merender komponen lambat itu, hanya dengan nilai lama, dan mengetik tetap terasa lambat.

Apa bedanya useDeferredValue dan useTransition?

useTransition membungkus pembaruan state, jadi kamu butuh akses ke setter-nya. useDeferredValue membungkus sebuah nilai, jadi hook ini berfungsi saat kamu hanya menerima nilainya, misalnya sebagai prop atau dari hook yang tidak kamu kendalikan.

Apakah useDeferredValue sama dengan debounce?

Tidak. Tidak ada jeda tetap. React memulai render deferred tepat setelah render mendesak, dan di perangkat yang cepat ketertinggalannya hampir tidak terlihat. Hook ini juga tidak mengurangi request jaringan dengan sendirinya.

Apa argumen kedua dari useDeferredValue?

Sejak React 19 kamu bisa memberikan initialValue. Di render pertama, hook mengembalikan nilai itu, lalu React merender lagi di latar belakang dengan nilai yang sebenarnya.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai