ריאקטNext.js

React use() ו-Suspense: זרימת נתונים ללא מפלים

React use() ו-Suspense: זרימת נתונים ללא מפלים

למפל יש תכונה חשבונית מכוערת. ארבע בקשות של 300ms בשרשרת הן 1.2 שניות של מסך ריק לנתונים שיכלו להגיע ב-300ms.

מה קומפוננטה מושהית עושה בפועל

קומפוננטה שאינה מוכנה זורקת Promise. לא שגיאה, Promise. React תופס אותו כפי ש-Error Boundary תופסת שגיאות, ממחזר את העבודה שנוצרה חלקית, מציג את ה-fallback הקרוב ביותר ומנוי ל-Promise שנזרק. כשהוא נפתר, React מריץ את הקומפוננטה שוב מההתחלה.

ה-Promise חייב להיות יציב בין הריצות הללו. Promise חדש בכל ריצה הוא השהיה אינסופית. זוהי הדרך הנפוצה ביותר לשבור use().

tsx

"use client";
import { use } from "react";
export default function Posts({ posts }) {
// מושהה עד שה-promise נפתר
const allPosts = use(posts);
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}

Suspense, הקליפה הסטטית ו-HTML שמגיע בסדר שגוי

React שולח את הקליפה הסטטית מיידית. החלק המאוחר מתווסף לסוף המסמך, מחוץ לשדה הראייה, ו-React מזרים <script> קטן ומוטמע לצדו להעברת התוכן למיקום הנכון.

tsx

import { Suspense } from "react";
export default function Page() {
return (
<main>
<h1>דשבורד</h1> {/* נשלח מיידית */}
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<Feed />
</Suspense>
</main>
);
}

התחל מוקדם, קרא מאוחר

tsx

import { Suspense } from "react";
import Posts from "@/app/ui/posts";
import Comments from "@/app/ui/comments";
export default function Page() {
// שתי הבקשות מתחילות עכשיו, במקביל
const posts = getPosts();
const comments = getComments();
return (
<>
<Suspense fallback={<div>טוען פוסטים...</div>}>
<Posts posts={posts} />
</Suspense>
<Suspense fallback={<div>טוען תגובות...</div>}>
<Comments comments={comments} />
</Suspense>
</>
);
}

המלכודת שאף אחד לא מצפה לה: זרימה משתמשת בקוד HTTP status שלך

כש-Suspense fallback מרונדר והזרם נפתח, השרת כבר התחייב ל-200 OK. notFound() שמופעל בתוך גבול שמוזרם לא יכול לתת 404 אמיתי.

tsx

export default async function PostPage({ params }) {
const { slug } = await params;
const exists = await checkSlugExists(slug); // מהיר, לפני פתיחת הזרם
if (!exists) notFound(); // 404 אמיתי
return (
<Suspense fallback={<PostSkeleton />}>
<PostContent slug={slug} />
</Suspense>
);
}

מה הזרימה עושה ל-Core Web Vitals

  • TTFB יורד לבערך זמן הרינדור של הפריסה.
  • LCP עלול להחמיר, לא להשתפר. שמור אלמנטי LCP בקליפה הסטטית.
  • CLS הוא האחריות שלך.
  • INP משתפר הודות ל-selective hydration.

מלכודות ושיטות עבודה מומלצות

  • לעולם אל תצור Promise במהלך הרינדור של Client Component.
  • Suspense תופסת המתנה, לא שגיאות. צמד כל גבול עם Error Boundary.
  • שנה את גודל הגבול כפי שאדם יתפוס אותו.
  • בוטים לא מקבלים את הזרם.
  • Promise prop עובר דרך הרשת.

סיכום

במקום רצף — בקשה, המתנה, רינדור, בקשה, המתנה, רינדור — אתה מקבל זרמים מקבילים: הכל מתחיל בבת אחת, וכל חלק בדף מופיע ברגע שהנתונים שלו מגיעים.