React use() ja Suspense: datan suoratoisto ilman vesiputouksia
React use() ja Suspense: datan suoratoisto ilman vesiputouksia
Vesiputoksella on ruma aritmeettinen ominaisuus. Neljä 300ms:n pyyntöä ketjussa on 1,2 sekuntia tyhjää näyttöä datalle joka olisi voinut saapua 300ms:ssa.
Mitä suspensoitunut komponentti oikeasti tekee
Komponentti joka ei ole valmis heittää Promisen. Ei virheen, Promisen. React nappaa sen kuten Error Boundary nappaa virheen, hylkää osittain renderöidyn työn, näyttää lähimmän fallbackin ja tilaa heitetyn Promisen. Kun se ratkeaa, React ajaa komponentin uudelleen alusta.
Promisen täytyy olla vakaa näiden ajojen välillä. Uusi Promise jokaisella ajolla on loputon suspensio. Tämä on yleisin tapa pilata use().
tsx
"use client";import { use } from "react";export default function Posts({ posts }) {// suspensoi kunnes promise ratkeaaconst allPosts = use(posts);return (<ul>{allPosts.map((post) => (<li key={post.id}>{post.title}</li>))}</ul>);}
Suspense, staattinen kuori ja väärässä järjestyksessä saapuva HTML
React lähettää staattisen kuoren välittömästi. Viivästynyt osa lisätään dokumentin loppuun, poissa näkyvistä, ja React suoratoistaa pienen inline <script>-elementin sen viereen siirtääkseen sisällön oikeaan paikkaan.
tsx
import { Suspense } from "react";export default function Page() {return (<main><h1>Kojelauta</h1> {/* lähetetään välittömästi */}<Suspense fallback={<StatsSkeleton />}><Stats /></Suspense><Suspense fallback={<FeedSkeleton />}><Feed /></Suspense></main>);}
Aloita aikaisin, lue myöhään
tsx
import { Suspense } from "react";import Posts from "@/app/ui/posts";import Comments from "@/app/ui/comments";export default function Page() {// molemmat pyynnöt alkavat nyt, rinnakkainconst posts = getPosts();const comments = getComments();return (<><Suspense fallback={<div>Ladataan julkaisuja...</div>}><Posts posts={posts} /></Suspense><Suspense fallback={<div>Ladataan kommentteja...</div>}><Comments comments={comments} /></Suspense></>);}
Ansa jota kukaan ei odota: suoratoisto käyttää HTTP-tilakoodi
Kun Suspense-fallback renderöidään ja virta avautuu, palvelin on jo sitoutunut 200 OK:een. notFound() joka laukeaa sisällä suoratoistetussa rajassa ei pysty antamaan aitoa 404:ää.
tsx
export default async function PostPage({ params }) {const { slug } = await params;const exists = await checkSlugExists(slug); // nopeasti, ennen virran avaamistaif (!exists) notFound(); // aito 404return (<Suspense fallback={<PostSkeleton />}><PostContent slug={slug} /></Suspense>);}
Mitä suoratoisto tekee Core Web Vitals -arvoille
- TTFB laskee noin layoutin renderöimisaikaan.
- LCP voi heiketä, ei parantua. Pidä LCP-elementit staattisessa kuoressa.
- CLS on sinun vastuullasi.
- INP paranee valikoivan hydraation ansiosta.
Sudenkuopat ja parhaat käytännöt
- Älä koskaan luo Promiset Client Componentin renderöinnin aikana.
- Suspense nappaa odottamisen, ei virheitä. Yhdistä jokainen raja Error Boundary:llä.
- Mitoita raja kuin henkilö tekisi sen.
- Botit eivät saa virtaa.
- Promise-proppi kulkee verkon yli.
Yhteenveto
Ketjun — pyyntö, odotus, renderöinti, pyyntö, odotus, renderöinti — sijaan saat rinnakkaiset virrat: kaikki alkaa kerralla, ja jokainen sivu näkyy heti kun sen oma data saapuu.