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 עובר דרך הרשת.
סיכום
במקום רצף — בקשה, המתנה, רינדור, בקשה, המתנה, רינדור — אתה מקבל זרמים מקבילים: הכל מתחיל בבת אחת, וכל חלק בדף מופיע ברגע שהנתונים שלו מגיעים.