<Suspense>
<Suspense>
memungkinkan Anda menampilkan fallback sampai komponen children selesai dimuat.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
- Referensi
- Pengunaan
- Menampilkan fallback saat konten sedang dimuat
- Mengungkap konten secara bersamaan sekaligus
- Mengungkap konten yang tersusun saat dimuat
- Menampilkan konten yang sudah basi saat konten baru sedang dimuat
- Mencegah konten yang sudah terungkap agar tidak disembunyikan
- Mengindikasikan bahwa transisi sedang terjadi
- Menyetel ulang batas Suspense pada navigasi
- Menyediakan fallback untuk kesalahan server dan konten khusus server
- Pemecahan Masalah
Referensi
<Suspense>
Props
children
: UI aktual yang ingin Anda render. Jikachildren
ditangguhkan sewaktu merender, batas Suspense akan beralih merenderfallback
.fallback
: UI alternatif untuk dirender menggantikan UI yang sebenarnya jika belum selesai dimuat. Setiap node React yang valid akan diterima, meskipun dalam praktiknya, fallback adalah tampilan placeholder yang ringan, Suspense akan secara otomatis beralih kefallback
ketikachildren
ditangguhkan, dan kembali kechildren
ketika datanya sudah siap. Jikafallback
ditangguhkan sewaktu melakukan rendering, itu akan mengaktifkan induk terdekat dari batas Suspense.
Catatan Penting
- React tidak menyimpan state apa pun untuk render yang ditangguhkan sebelum dapat dimuat untuk pertama kalinya. Ketika komponen sudah dimuat, React akan mencoba merender ulang komponen yang ditangguhkan dari awal.
- Jika Suspense menampilkan konten untuk komponen, namun kemudian ditangguhkan lagi,
fallback
akan ditampilkan lagi kecuali jika pembaruan yang menyebabkannya, disebabkan olehstartTransition
atauuseDeferredValue
. - Jika React perlu menyembunyikan konten yang sudah terlihat karena ditangguhkan lagi, ini akan membersihkan layout Effects yang ada di dalam konten komponen. Ketika konten siap untuk ditampilkan lagi, React akan menjalankan Efek tata letak lagi. Hal ini memastikan bahwa Efek yang mengukur tata letak DOM tidak mencoba melakukan hal ini saat konten disembunyikan.
- React menyertakan pengoptimalan di under the hood Streaming Server Rendering dan Selective Hydration yang terintegrasi dengan Suspense. Baca tinjauan arsitektural dan tonton sebuah pembicaraan teknis untuk belajar lebih lanjut.
Pengunaan
Menampilkan fallback saat konten sedang dimuat
Anda dapat membungkus bagian mana pun dari aplikasi Anda dengan Batas Suspense:
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
React akan menampilkan kode loading fallback sampai semua kode dan data yang dibutuhkan oleh the children telah dimuat.
Pada contoh di bawah ini, Komponen Albums
menangguhkan saat mengambil daftar album. Hingga siap untuk dirender, React mengganti batas Suspense terdekat di atas untuk menunjukkan fallback—Anda Loading
Komponen. Kemudian, saat data dimuat, React menyembunyikan fallback Loading
dan merender komponen Albums
dengan data.
import { Suspense } from 'react'; import Albums from './Albums.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </> ); } function Loading() { return <h2>🌀 Loading...</h2>; }
Mengungkap konten secara bersamaan sekaligus
Secara default, seluruh pohon di dalam Suspense diperlakukan sebagai satu kesatuan. Sebagai contoh, meskipun hanya satu dari komponen-komponen ini yang tertahan menunggu beberapa data, semua komponen tersebut akan digantikan oleh indikator pemuatan:
<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>
Kemudian, setelah semuanya siap untuk ditampilkan, semuanya akan muncul sekaligus.
Pada contoh di bawah ini, baik Biography
dan Album
mengambil beberapa data. Namun, karena dikelompokkan di bawah satu batas Suspense, komponen-komponen ini selalu “muncul” bersamaan.
import { Suspense } from 'react'; import Albums from './Albums.js'; import Biography from './Biography.js'; import Panel from './Panel.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Biography artistId={artist.id} /> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </> ); } function Loading() { return <h2>🌀 Loading...</h2>; }
Komponen yang memuat data tidak harus menjadi anak langsung dari batas Suspense. Sebagai contoh, Anda dapat memindahkan Biografi
dan Album
ke dalam komponen Rincian
yang baru. Hal ini tidak akan mengubah perilakunya. Biografi
dan Albums
memiliki batas Suspense induk terdekat yang sama, sehingga pengungkapannya dikoordinasikan bersama.
<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>
function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}
Mengungkap konten yang tersusun saat dimuat
Ketika sebuah komponen ditangguhkan, komponen Suspense induk terdekat akan menampilkan fallback. Hal ini memungkinkan Anda menyatukan beberapa komponen Suspense untuk membuat urutan pemuatan. Fallback setiap batas Suspense akan terisi saat tingkat konten berikutnya tersedia. Sebagai contoh, Anda dapat memberikan daftar album dengan fallback tersendiri:
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
Dengan perubahan ini, menampilkan Biography
tidak perlu “menunggu” sampai Album
dimuat.
Urutan nya adalah sebagai berikut:
- Jika
Biography
belum dimuat,BigSpinner
ditampilkan sebagai pengganti seluruh area konten. - Setelah
Biography
selesai dimuat,BigSpinner
digantikan oleh konten. - Jika
Albums
belum dimuat,AlbumsGlimmer
ditampilkan sebagai penggantiAlbums
dan induknyaPanel
. - Akhirnya, setelah
Albums
selesai dimuat, dia akan menggantikanAlbumsGlimmer
.
import { Suspense } from 'react'; import Albums from './Albums.js'; import Biography from './Biography.js'; import Panel from './Panel.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<BigSpinner />}> <Biography artistId={artist.id} /> <Suspense fallback={<AlbumsGlimmer />}> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </Suspense> </> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; } function AlbumsGlimmer() { return ( <div className="glimmer-panel"> <div className="glimmer-line" /> <div className="glimmer-line" /> <div className="glimmer-line" /> </div> ); }
Batas suspense memungkinkan Anda mengoordinasikan bagian mana dari UI Anda yang harus selalu “muncul” bersamaan, dan bagian mana yang harus menampilkan lebih banyak konten secara bertahap dalam urutan status pemuatan. Anda dapat menambah, memindahkan, atau menghapus batas-batas Suspense di mana saja di dalam pohon tanpa memengaruhi perilaku aplikasi Anda yang lain.
Jangan memberikan batas Suspense pada setiap komponen. Batas suspense tidak boleh lebih terperinci daripada urutan pemuatan yang Anda inginkan untuk dialami pengguna. Jika Anda bekerja dengan desainer, tanyakan kepada mereka di mana status pemuatan harus ditempatkan - kemungkinan mereka sudah memasukkannya dalam wireframe desain mereka.
Menampilkan konten yang sudah basi saat konten baru sedang dimuat
Dalam contoh ini, komponen SearchResults
ditangguhkan saat mengambil hasil pencarian. Ketik "a"
, tunggu untuk hasil, dan kemudian edit menjadi "ab"
. Hasil untuk "a"
akan tergantikan oleh loading fallback.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Pola UI alternatif yang umum adalah untuk menunda memperbarui daftar dan terus menampilkan hasil sebelumnya hingga hasil yang baru siap. The useDeferredValue
Hook memungkinkan Anda meneruskan versi kueri yang ditangguhkan:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
query
akan segera diperbarui, sehingga input akan menampilkan nilai baru. Namun, deferredQuery
akan menyimpan nilai sebelumnya sampai data dimuat, jadi SearchResults
akan menunjukkan hasil yang sebelumnya untuk sementara waktu.
Untuk membuatnya lebih jelas bagi pengguna, Anda bisa menambahkan indikasi visual apabila daftar hasil basi ditampilkan:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>
Masukkan "a"
didalam contoh berikut ini, tunggu hingga hasilnya dimuat, lalu edit input ke "ab"
. Perhatikan, bahwa alih-alih fallback Suspense, Anda sekarang melihat daftar hasil sebelumnya yang diredupkan sampai hasil yang 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); const isStale = query !== deferredQuery; return ( <> <label> Search albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Loading...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1 }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
Mencegah konten yang sudah terungkap agar tidak disembunyikan
Ketika sebuah komponen ditangguhkan, batas Suspense induk terdekat akan beralih untuk menampilkan fallback. Hal ini dapat menyebabkan pengalaman pengguna yang mengejutkan jika komponen tersebut sudah menampilkan beberapa konten. Coba tekan tombol ini:
import { Suspense, useState } 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('/'); function navigate(url) { 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> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; }
Saat Anda menekan tombol, Router
komponen merender ArtistPage
sebagai gantinya IndexPage
.Komponen di dalam ArtistPage
tertangguhkan, sehingga batas Suspense terdekat mulai menunjukkan fallback. Batas Suspense terdekat berada di dekat root, sehingga seluruh tata letak situs diganti dengan BigSpinner
.
Untuk mencegah hal ini, Anda dapat menandai pembaruan status navigasi sebagai transition dengan startTransition
:
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Hal ini memberi tahu React bahwa transisi state tidak mendesak, dan lebih baik tetap menampilkan halaman sebelumnya daripada menyembunyikan konten yang sudah ditampilkan. Sekarang klik tombol “menunggu” sampai Biography
dimuat:
import { Suspense, startTransition, useState } 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('/'); 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> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; }
Transisi tidak menunggu semua konten dimuat. Ini hanya menunggu cukup lama untuk menghindari menyembunyikan konten yang sudah terungkap. Misalnya, situs web Layout
sudah terungkap, jadi tidak baik menyembunyikannya di balik loading spinner. Namun, batas Suspense
yang bersusun di sekitar Albums
adalah hal yang baru, jadi transisinya tidak perlu ditunggu.
Mengindikasikan bahwa transisi sedang terjadi
Pada contoh di atas, setelah Anda mengeklik tombol, tidak ada indikasi visual bahwa navigasi sedang berlangsung. Untuk menambahkan indikator, Anda dapat mengganti startTransition
dengan useTransition
yang akan memberimu boolean dengan nilai isPending
. Pada contoh di bawah ini, ini digunakan untuk mengubah gaya tajuk situs web saat transisi terjadi:
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>; }
Menyetel ulang batas Suspense pada navigasi
Selama transisi, React akan menghindari menyembunyikan konten yang sudah ditampilkan. Namun, jika Anda menavigasi ke rute dengan parameter yang berbeda, Anda mungkin ingin memberi tahu React bahwa itu adalah konten yang berbeda. Anda dapat mengekspresikan ini dengan sebuah key
:
<ProfilePage key={queryParams.id} />
Bayangkan Anda sedang menavigasi dalam halaman profil pengguna, dan ada sesuatu yang ditangguhkan. Jika pembaruan itu dibungkus dengan transisi, pembaruan itu tidak akan memicu kemunduran untuk konten yang sudah terlihat. Itulah perilaku yang diharapkan.
Namun, sekarang bayangkan Anda menavigasi di antara dua profil pengguna yang berbeda. Dalam hal ini, masuk akal untuk menampilkan fallback. Sebagai contoh, timeline salah satu pengguna adalah konten yang berbeda dengan timeline pengguna lain. Dengan menentukan sebuah kunci
, Anda memastikan bahwa React memperlakukan profil pengguna yang berbeda sebagai komponen yang berbeda, dan menyetel ulang batas-batas Suspense selama navigasi. Router yang terintegrasi dengan Suspense seharusnya melakukan ini secara otomatis.
Menyediakan fallback untuk kesalahan server dan konten khusus server
Jika Anda menggunakan salah satu dari API perenderan server streaming (atau kerangka kerja yang bergantung pada mereka), React juga akan menggunakan <Suspense>
untuk menangani kesalahan pada server. Jika sebuah komponen menimbulkan kesalahan pada server, React tidak akan membatalkan render server. Sebagai gantinya, React akan menemukan komponen <Suspense>
terdekat di atasnya dan menyertakan fallback-nya (seperti spiner) ke dalam HTML server yang dihasilkan. Pengguna akan melihat pemintal pada awalnya.
Pada klien, React akan mencoba merender komponen yang sama lagi. Jika terjadi kesalahan pada klien juga, React akan melemparkan kesalahan dan menampilkan [ErrorBoundary terdekat.] (/reference/react/Component/Component#static-getderivedstatefromerror) Namun, jika tidak terjadi kesalahan pada klien, React tidak akan menampilkan kesalahan pada pengguna karena konten pada akhirnya berhasil ditampilkan.
Anda dapat menggunakan ini untuk mengecualikan beberapa komponen dari perenderan di server. Untuk melakukan hal ini, lemparkan kesalahan pada lingkungan server dan kemudian bungkus dengan batas <Suspense>
untuk mengganti HTML-nya dengan fallback:
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}
HTML server akan menyertakan indikator pemuatan. Indikator ini akan digantikan oleh komponen Chat
pada klien.
Pemecahan Masalah
Bagaimana cara mencegah agar UI tidak diganti dengan fallback selama pembaruan?
Mengganti UI yang terlihat dengan fallback menciptakan pengalaman pengguna yang mengejutkan. Hal ini dapat terjadi ketika pembaruan menyebabkan sebuah komponen ditangguhkan, dan batas Suspense terdekat sudah menampilkan konten kepada pengguna.
Untuk mencegah hal ini terjadi, tandai pembaruan sebagai tidak mendesak menggunakan startTransition
. Selama transisi, React akan menunggu hingga cukup banyak data yang dimuat untuk mencegah terjadinya fallback yang tidak diinginkan:
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
Ini akan menghindari menyembunyikan konten yang ada. Namun, setiap batas Suspense
yang baru dirender masih akan segera menampilkan fallback untuk menghindari pemblokiran UI dan membiarkan pengguna melihat konten saat tersedia.
React hanya akan mencegah fallback yang tidak diinginkan selama pembaruan yang tidak mendesak. Ini tidak akan menunda render jika itu adalah hasil dari pembaruan yang mendesak. Anda harus ikut serta dengan API seperti startTransition
atau useDeferredValue
.
Jika router Anda terintegrasi dengan Suspense, router akan membungkus pembaruannya menjadi startTransition
secara otomatis.