useTransition
useTransition
adalah sebuah React Hook yang memungkinkan Anda merubah suatu state tanpa memblokir UI.
const [isPending, startTransition] = useTransition()
Referensi
useTransition()
Panggil useTransition
pada level teratas komponen Anda untuk menandai beberapa perubahan state sebagai transisi.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
Lihat contoh lainnya dibawah ini.
Parameters
useTransition
tidak menerima parameter apapun.
Returns
useTransition
mengembalikan senarai dengan tepat dua item:
- Penanda
isPending
yang memberitahukan Anda bahwa terdapat transisi yang tertunda. - fungsi
startTransition
yang memungkinkan Anda menandai perubahan state sebagai transisi.
fungsi startTransition
Fungsi startTransition
yang dikembalikan oleh useTransition
memungkinkan Anda menandai perubahan state sebagai transisi.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Parameters
scope
: Fungsi yang mengubah beberapa state dengan memanggil satu atau lebih fungsiset
. React segera memanggilscope
dengan tanpa parameter dan menandai semua perubahan state yang dijadwalkan secara sinkron saat fungsiscope
dipanggil sebagai transisi. Mereka akan menjadi non-blocking dan tidak akan menampilkan indikator render yang tidak perlu.
Returns
startTransition
tidak mengembalikan apapun.
Perhatian
-
useTransition
adalah sebuah Hook, sehingga hanya bisa dipanggil di dalam komponen atau Hook custom. Jika Anda ingin memulai sebuah transisi di tempat lain (contoh, dari data library), sebaiknya panggilstartTransition
sebagai gantinya. -
Anda dapat membungkus perubahan menjadi transisi hanya jika Anda memiliki akses pada fungsi
set
pada state tersebut. Jika Anda ingin memulai sebuah transisi sebagai balasan dari beberapa prop atau nilai Hook custom, coba gunakanuseDeferredValue
sebagai gantinya. -
Fungsi yang Anda kirimkan kepada
startTransition
haruslah sinkron. React akan langsung mengeksekusi fungsi ini, menandai semua perubahan state yang terjadi sambil mengeksekusinya sebagai transisi. Jika Anda mencoba untuk melakukan perubahan state lebih nanti (contoh, saat timeout), mereka tidak akan ditandai sebagai transisi. -
Perubahan state yang ditandai sebagai transisi akan terganggu oleh perubahan state lainnya. Contohnya, jika anda mengubah komponen chart di dalam transisi, namun kemudian memulai mengetik dalam input ketika chart sedang di tengah merender ulang, React akan merender ulang pekerjaan pada komponen chart setelah mengerjakan perubahan pada input.
-
Perubahan transisi tidak dapat digunakan untuk mengontrol input teks.
-
Apabila terdapat beberapa transisi yang berjalan, React saat ini akan mengelompokkan mereka bersama. Ini adalah limitasi yang mungkin akan dihapus pada rilis yang akan datang.
Kegunaan
Menandai perubahan state sebagai transisi non-blocking
Panggil useTransition
pada level teratas komponen Anda untuk menandai perubahan state sebagai transisi non-blocking.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition
mengembalikan sebuah senarai dengan tepat dua item:
- Penanda
isPending
yang memberitahukan Anda apakah terdapat transisi tertunda. - Fungsi
startTransition
yang memungkinkan Anda menandai perubahan state sebagai transisi.
Kemudian Anda dapat menandai perubahan state sebagai transisi seperti berikut:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Transisi akan memungkinkan Anda untuk mempertahankan perubahan tampilan pengguna secara responsif bahkan untuk perangkat lambat.
Dengan transisi, UI Anda akan tetap responsif di tengah-tengah me-render ulang. Contohnya, jika pengguna menekan tab namun mereka berubah pikiran dan menekan tab lain, mereka dapat melakukan itu tanpa menunggu muat ulang pertama selesai.
Example 1 of 2: Merubah tab saat ini dalam transisi
Pada contoh berikut ini, tab “Posts” ini Dipelankan secara artifisial sehingga akan memakan waktu setidaknya satu detik untuk render.
Tekan “Posts” kemudian segera tekan “Contact”. Perhatikan bahwa ini akan mengganggu muatan “Posts” yang lambat. Tab “Contact” akan tampil segera. Karena perubahan state ini ditandai sebagai transisi, merender ulang yang lambat tidak akan membekukan tampilan pengguna.
import { useState, useTransition } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } return ( <> <TabButton isActive={tab === 'about'} onClick={() => selectTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')} > Posts (slow) </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => selectTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </> ); }
Merubah komponen induk dalam transisi
Anda dapat mengubah state komponen induk dari panggilan useTransition
juga. Contohnya, komponen TabButton
ini membungkus logika komponen onClick
dalam sebuah transisi:
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
}
Karena komponen induk merubah statenya di dalam event handler onClick
, perubahan state tersebut akan ditandai sebagai transisi. Inilah mengapa, seperti pada contoh di awal, Anda dapat menekan pada “Posts” dan kemudian segera menekan “Contact”. Mengubah tab yang dipilih akan ditandai sebagai transisi, sehingga itu tidak memblokir tampilan pengguna.
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Menampilan state visual tertunda saat transisi
Anda dapat menggunakan nilai boolean isPending
yang dikembalikan oleh useTransition
untuk menandai ke pengguna bahwa transisi sedang berjalan. Contohnya, tombol tab dapat memiliki state visual special “pending”:
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...
Perhatikan bagaimana menekan “Posts” sekarang terasa lebih responsif karena tombol tab tersebut berubah langsung:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Mencegah indikator loading yang tidak diinginkan
Pada contoh berikut ini, komponen PostsTab
mengambil beberapa data menggunakan Suspense-enabled data source. Ketika Anda menekan tab “Posts”, komponen PostsTab
akan disuspends, menyebabkan fallback loading terdekat untuk muncul:
import { Suspense, useState } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [tab, setTab] = useState('about'); return ( <Suspense fallback={<h1>🌀 Loading...</h1>}> <TabButton isActive={tab === 'about'} onClick={() => setTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => setTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </Suspense> ); }
Menyembunyikan seluruh tab container untuk menampilkan indikator loading akan mengarahkan ke pengalaman pengguna yang gemuruh. Jika Anda menambahkan useTransition
ke TabButton
, Anda bisa sebagai gantinya mengindikasi tampilan state pending di tombol tab sebagai gantinya.
Perhatikan bahwa menekan “Posts” tidak menjadikan seluruh tab container dengan spinner:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Baca lebih lanjut tentang menggunakan transisi dengan Suspense.
Membangun router Suspense-enabled
Jika Anda membangun React framework atau router, kami merekomendasikan menandai navigasi halaman sebagai transisi.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Ini direkomendasikan karena dua alasan:
- Transisi dapat terputus, yang memungkinkan pengguna mengklik tanpa menunggu me-render ulang selesai.
- Transisi mencegah indikator loading yang tidak diinginkan, yang memungkinkan pengguna menghindari lompatan menggelegar pada navigasi.
Berikut adalah contoh router kecil sederhana menggunakan transisi untuk navigasi.
import { Suspense, useState, useTransition } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; }
Pemecahan Masalah
Merubah input dalam transisi tidak bekerja
Anda tidak dapat menggunakan transisi unttuk variabel state yang mengendalikan input:
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ Can't use transitions for controlled input state
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;
Ini dikarenakan transisi adalah non-blocking, namun mengubah input dalam respon untuk mengubah event seharusnya bekerja secara sinkron. Jika Anda ingin menjalankan transisi sebagai respon untuk menulis, Anda memiliki dua opsi:
- Anda dapat mendeklarasikan dua variabel state berbeda: satu untuk state masukan ( yang selalu berubah secara sinkron), dan satu yang akan Anda ubah dalam transisi. Ini memungkinkan Anda mengendalikan masukan menggunakan state sinkron, dan mengirim variabel state transisi (yang akan “lag” dibelakang masukan) ke sisa logika rendering Anda.
- Kalau tidak, Anda dapat memiliki satu variabel state, dan tambahkan
useDeferredValue
yang akan “lag” dibelakang nilai asli. Ini akan mentrigger merender ulang non-blocking untuk “mengejar” dengan nilai baru secara otomatis.
React tidak memperlakukan perubahan state saya sebagai transisi
Ketika Anda membungkus perubahan state di dalam transisi, pastikan bahwa itu terjadi saat memanggil startTransition
:
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
Fungsi yang Anda kirimkan ke startTransition
harus sinkron.
Anda tidak dapat menandakan perubahan sebagai transisi seperti berikut:
startTransition(() => {
// ❌ Setting state *after* startTransition call
setTimeout(() => {
setPage('/about');
}, 1000);
});
Sebaiknya, anda dapat melakukan hal berikut:
setTimeout(() => {
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
}, 1000);
Demikian pula, Anda tidak dapat menandai perubahan sebagai transisi seperti berikut:
startTransition(async () => {
await someAsyncFunction();
// ❌ Setting state *after* startTransition call
setPage('/about');
});
Namun, ini bekerja sebagai gantinya:
await someAsyncFunction();
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
Saya ingin memanggil useTransition
dari luar komponen
Anda tidak dapat memanggil useTransition
di luar sebuah komponen karena ini adalah sebuah Hook. Dalam kasus ini, sebaiknya gunakanlah method startTransition
. Itu bekerja dengan cara yang sama, namun itu tidak dapat memberikan indikator isPending
.
Fungsi yang saya berikan ke startTransition
tereksekusi langsung
Jika Anda menjalankan kode berikut, ini akan mencetak 1, 2, 3:
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);
Ini diharapkan untuk mencetak 1, 2, 3. Fungsi yang Anda berikan ke startTransition
tidak tertunda. Tidak seperti milik browser setTimeout
, hal tersebut nantinya tidak menjalankan callback. React akan eksekusi fungsi Anda secara langsung, namun perubahan state yang terjadwal saat berjalan akan ditandai sebagai transisi. Anda dapat membayangkan hal tersebut bekerja seperti berikut:
// A simplified version of how React works
let isInsideTransition = false;
function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}
function setState() {
if (isInsideTransition) {
// ... schedule a transition state update ...
} else {
// ... schedule an urgent state update ...
}
}