reactnext.js

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 ratkeaa
const 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, rinnakkain
const 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 avaamista
if (!exists) notFound(); // aito 404
return (
<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.