<Suspense>
<Suspense> מאפשר לך להציג סתירה עד שהילדים שלו יסיימו לטעון.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>הפניה
<Suspense>
אבזרים
children: ממשק המשתמש בפועל שאתה מתכוון לעבד. אםchildrenמושהה בזמן העיבוד, הגבול Suspense יעבור לעיבודfallback.fallback: ממשק משתמש חלופי לעיבוד במקום ממשק המשתמש בפועל אם הוא לא סיים לטעון. כל צומת React חוקי מתקבל, אם כי בפועל, סתירה היא תצוגת מציין מיקום קלת משקל, כגון ספינר טעינה או שלד. Suspense יעבור אוטומטית ל-fallbackכאשרchildrenיושעה, ויחזור ל-childrenכאשר הנתונים יהיו מוכנים. אםfallbackמושהה בזמן העיבוד, זה יפעיל את גבול האב הקרוב ביותר Suspense.
אזהרות
- React אינו משמר שום state עבור עיבודים שהושעו לפני שהם הצליחו לעלות בפעם הראשונה. כאשר הרכיב נטען, React ינסה לעבד את העץ התלוי מאפס.
- אם Suspense הציג תוכן עבור העץ, אבל אז הוא הושעה שוב, ה-
fallbackיוצג שוב אלא אם העדכון שגרם לו היה caused על ידיstartTransitionאוuseDeferredValue. - אם React צריך להסתיר את התוכן שכבר נראה לעין בגלל שהוא הושעה שוב, הוא ינקה את אפקטי הפריסה בעץ התוכן. כאשר התוכן יהיה מוכן להצגה שוב, React יפעיל שוב את אפקטי הפריסה. זה מבטיח שאפקטים המודדים את פריסת DOM לא ינסו לעשות זאת בזמן שהתוכן מוסתר.
- React כולל אופטימיזציות מתחת למכסה המנוע כמו עיבוד שרת זרימה ו-הידרציה סלקטיבית המשולבות עם Suspense. קרא סקירה אדריכלית וצפה בשיחה טכנית למידע נוסף.
שימוש
הצגת מיתוס בזמן טעינת תוכן
אתה יכול לעטוף כל חלק ביישום שלך עם גבול Suspense:
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>React יציג את חזרת הטעינה שלך עד שכל הקוד והנתונים הדרושים ל-הילדים ייטענו.
בדוגמה למטה, הרכיב Albums משהה בזמן שליפת רשימת האלבומים. עד שהוא מוכן לעיבוד, React מחליף את גבול Suspense הקרוב ביותר למעלה כדי להציג את ה-fallback - רכיב ה-Loading שלך. לאחר מכן, כשהנתונים נטענים, React מסתיר את Loading החזרה ומעבד את הרכיב Albums עם נתונים.
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>; }
חשיפת תוכן יחד בבת אחת
כברירת מחדל, העץ כולו בתוך Suspense מטופל כיחידה אחת. לדוגמה, גם אם רק אחד מהרכיבים האלה משעה המתנה לכמה נתונים, כולם יחד יוחלפו במחוון הטעינה:
<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>לאחר מכן, לאחר שכולם יהיו מוכנים להצגה, כולם יופיעו יחד בבת אחת.
בדוגמה למטה, גם Biography וגם Albums מביאים נתונים מסוימים. עם זאת, מכיוון שuse הם מקובצים תחת גבול Suspense יחיד, הרכיבים האלה תמיד “קופצים” יחד בו זמנית.
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>; }
רכיבים שטוענים נתונים אינם חייבים להיות ילדים ישירים של גבול Suspense. לדוגמה, אתה יכול להעביר את Biography ו-Albums לרכיב Details חדש. זה לא משנה את ההתנהגות. Biography וAlbums חולקים את אותו גבול הורה Suspense הקרוב ביותר, כך שהגילוי שלהם מתואם יחד.
<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>
function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}חשיפת תוכן מקונן בזמן שהוא נטען
כאשר רכיב מושהה, רכיב האב הקרוב ביותר Suspense מציג את ה-fallback. זה מאפשר לך לקנן רכיבי Suspense מרובים כדי ליצור רצף טעינה. כל נפילה של גבול Suspense יתמלא כאשר רמת התוכן הבאה תהיה זמינה. לדוגמה, אתה יכול להעניק לרשימת האלבומים נקודת מוצא משלה:
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>עם השינוי הזה, הצגת ה-Biography לא צריכה “לחכות” לטעינת ה-Albums.
הרצף יהיה:
- אם
Biographyעדיין לא נטען,BigSpinnerמוצג במקום כל אזור התוכן. - ברגע ש-
Biographyמסיים את הטעינה,BigSpinnerמוחלף בתוכן. - אם
Albumsעדיין לא נטען,AlbumsGlimmerמוצג במקוםAlbumsוהאב שלוPanel. - לבסוף, ברגע ש
Albumsמסיים את הטעינה, הוא מחליף אתAlbumsGlimmer.
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> ); }
גבולות Suspense מאפשרים לך לתאם אילו חלקים בממשק המשתמש שלך צריכים תמיד “לצץ” יחד בו זמנית, ואילו חלקים צריכים לחשוף בהדרגה יותר תוכן ברצף של טעינת states. אתה יכול להוסיף, להעביר או למחוק גבולות Suspense בכל מקום בעץ מבלי להשפיע על שאר התנהגות האפליקציה שלך.
אל תשים גבול Suspense סביב כל רכיב. גבולות Suspense לא צריכים להיות יותר מפורטים מרצף הטעינה שאתה רוצה שה-user יחווה. אם אתה עובד עם מעצב, שאל אותו היכן יש למקם את states הטעינה - סביר להניח שהם כבר כללו אותם במסגרות העיצוב שלהם.
מציג תוכן מיושן בזמן טעינת תוכן טרי
בדוגמה זו, הרכיב SearchResults מושהה בזמן שליפת תוצאות החיפוש. הקלד "a", המתן לתוצאות ולאחר מכן ערוך אותו ל-"ab". התוצאות עבור "a" יוחלפו על ידי החזרה לטעינה.
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> </> ); }
דפוס משתמש חלופי נפוץ הוא לדחות את עדכון הרשימה ולהמשיך להציג את התוצאות הקודמות עד שהתוצאות החדשות יהיו מוכנות. ה-useDeferredValue Hook מאפשר לך להעביר גרסה נדחית של השאילתה למטה:
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 יתעדכן מיד, כך שהקלט יציג את הערך החדש. עם זאת, ה-deferredQuery ישמור על הערך הקודם שלו עד שהנתונים ייטענו, אז SearchResults יציג את התוצאות המעופשות לזמן מה.
כדי להפוך את זה לברור יותר ל-user, אתה יכול להוסיף אינדיקציה חזותית כאשר רשימת התוצאות המעופשת מוצגת:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>הזן "a" בדוגמה למטה, המתן לטעינת התוצאות ולאחר מכן ערוך את הקלט ל-"ab". שים לב כיצד במקום ה-fallback Suspense, אתה רואה כעת את רשימת התוצאות המעומעמת עד שהתוצאות החדשות ייטענו:
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> </> ); }
מניעת הסתרת תוכן שכבר נחשף
כאשר רכיב מושהה, גבול האב הקרוב ביותר Suspense עובר להצגת ה-fallback. זה יכול להוביל לחוויית user צורמת אם הוא כבר הציג תוכן כלשהו. נסה ללחוץ על הכפתור הזה:
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>; }
כאשר לחצת על הכפתור, הרכיב Router הציג ArtistPage במקום IndexPage. רכיב בתוך ArtistPage הושעה, כך שהגבול הקרוב ביותר של Suspense התחיל להראות את החזרה. הגבול Suspense הקרוב ביותר היה ליד השורש, כך שכל פריסת האתר הוחלפה ב-BigSpinner.
כדי למנוע זאת, תוכל לסמן את עדכון הניווט state כמעבר עם startTransition:
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...זה אומר לReact שהמעבר state אינו דחוף, ועדיף להמשיך ולהציג את הדף הקודם במקום להסתיר תוכן שכבר נחשף. כעת לחיצה על הכפתור “ממתין” לטעינת ה-Biography:
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>; }
מעבר לא מחכה לטעינת כל התוכן. זה רק מחכה מספיק זמן כדי להימנע מהסתרת תוכן שכבר נחשף. לדוגמה, האתר Layout כבר נחשף, אז זה יהיה רע להחביא אותו מאחורי ספינר טעינה. עם זאת, הגבול המקנן Suspense סביב Albums הוא חדש, כך שהמעבר לא מחכה לו.
מציין שמתרחש מעבר
בדוגמה שלמעלה, ברגע שאתה לוחץ על הכפתור, אין אינדיקציה ויזואלית לכך שניווט מתבצע. כדי להוסיף מחוון, אתה יכול להחליף את startTransition ב-useTransition שנותן לך ערך בוליאני isPending. בדוגמה שלהלן, זה used לשנות את סגנון הכותרת של האתר בזמן שמתרחש מעבר:
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>; }
איפוס גבולות Suspense בניווט
במהלך מעבר, React ימנע מהסתרת תוכן שכבר נחשף. עם זאת, אם אתה מנווט למסלול עם פרמטרים שונים, אולי תרצה לומר ל-React שזהו תוכן שונה. אתה יכול לבטא זאת באמצעות key:
<ProfilePage key={queryParams.id} />תאר לעצמך שאתה מנווט בתוך דף הפרופיל של user, ומשהו מושהה. אם העדכון הזה עטוף במעבר, הוא לא יפעיל את החזרה לתוכן שכבר גלוי. זו ההתנהגות הצפויה.
עם זאת, דמיין כעת שאתה מנווט בין שני פרופילי user שונים. במקרה כזה, הגיוני להראות את הנסיגה. לדוגמה, ציר הזמן של user אחד הוא תוכן שונה מציר הזמן של user אחר. על ידי ציון key, אתה מבטיח שReact מתייחס לפרופילים שונים של users כרכיבים שונים, ומאפס את גבולות Suspense במהלך הניווט. נתבים המשולבים ב-Suspense צריכים לעשות זאת באופן אוטומטי.
מתן מיתון עבור שגיאות שרת ותוכן ללקוח בלבד
אם אתה use אחד מ-שרת הזרימה המציג APIs (או מסגרת המסתמכת עליהם), React גם use גבולות <Suspense> שלך כדי לטפל בשגיאות בשרת. אם רכיב זורק שגיאה על השרת, React לא יבטל את עיבוד השרת. במקום זאת, הוא ימצא את רכיב <Suspense> הקרוב ביותר מעליו ויכלול את ה-fallback שלו (כגון ספינר) לתוך השרת שנוצר HTML. ה-user יראה ספינר בהתחלה.
בלקוח, React ינסה לעבד שוב את אותו רכיב. אם יש שגיאות גם בלקוח, React יזרוק את השגיאה ויציג את גבול השגיאה הקרוב ביותר. עם זאת, אם היא לא תופיע שגיאה בלקוח, React לא יציג את השגיאה ל-React בסופו של דבר מאז שהתוכן שהוצג בהצלחה.
אתה יכול use זה כדי לבטל את הסכמתם של רכיבים מסוימים לעיבוד בשרת. כדי לעשות זאת, זרוק שגיאה בסביבת השרת ולאחר מכן עטוף אותם בגבול <Suspense> כדי להחליף את ה-HTML שלהם ב-fallbacks:
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}השרת HTML יכלול את מחוון הטעינה. הוא יוחלף ברכיב Chat בלקוח.
פתרון בעיות
כיצד אוכל למנוע את החלפת ממשק המשתמש ב-fallback במהלך עדכון?
החלפת ממשק משתמש גלוי ב-fallback יוצרת חוויית user צורמת. זה יכול לקרות כאשר עדכון cause הוא רכיב להשעיה, והגבול Suspense הקרוב ביותר כבר מציג תוכן ל-user.
כדי למנוע את זה, סמן את העדכון כלא דחוף באמצעות startTransition. במהלך מעבר, React ימתין עד שיטענו מספיק נתונים כדי למנוע הופעת נסיגה לא רצויה:
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}זה ימנע הסתרת תוכן קיים. עם זאת, כל גבולות Suspense שיוצגו לאחרונה עדיין יציגו מיד נפילות כדי להימנע מחסימת ממשק המשתמש ולתת ל-user לראות את התוכן כאשר הוא הופך זמין.
React ימנע נפילות לא רצויות רק במהלך עדכונים לא דחופים. זה לא יעכב עיבוד אם זה תוצאה של עדכון דחוף. עליך להצטרף עם API כמו startTransition או useDeferredValue.
אם הנתב שלך משולב עם Suspense, הוא אמור לעטוף את העדכונים שלו לתוך startTransition באופן אוטומטי.