useTransition הוא React Hook המאפשר לך לעדכן את state מבלי לחסום את ממשק המשתמש.
const [isPending, startTransition] = useTransition()הפניה
useTransition()
התקשר ל-useTransition ברמה העליונה של הרכיב שלך כדי לסמן כמה עדכוני state כמעברים.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}פרמטרים
useTransition אינו לוקח פרמטרים כלשהם.
מחזירה
useTransition מחזיר מערך עם שני פריטים בדיוק:
- הדגל
isPendingשאומר לך אם יש מעבר ממתין. - הפונקציה
startTransitionהמאפשרת לסמן עדכון state כמעבר.
startTransition פונקציה
הפונקציה startTransition המוחזרת על ידי useTransition מאפשרת לך לסמן עדכון state כמעבר.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}פרמטרים
scope: פונקציה שמעדכנת חלק מה-state על ידי קריאה לפונקציה אחת או יותר שלset. React מתקשרת מיד לscopeללא פרמטרים ומסמנת את כלsetפונקציה אחת או יותר כפונקציה _TK_8 מעדכנת באופן סינכרוני את לוח הזמנים __T_3. הם יהיו לא חוסמים ולא יציגו מחווני טעינה לא רצויים.
מחזירה
startTransition לא מחזיר כלום.
אזהרות
-
useTransitionהוא Hook, כך שניתן לקרוא לו רק בתוך רכיבים או Hooks מותאם אישית. אם אתה צריך להתחיל מעבר במקום אחר (לדוגמה, מספריית נתונים), התקשר ל-startTransitionהעצמאי במקום זאת. -
אתה יכול לעטוף עדכון למעבר רק אם יש לך גישה לפונקציית
setשל אותו state. אם אתה רוצה להתחיל במעבר בתגובה לאביזר כלשהו או לערך Hook מותאם אישית, נסה אתuseDeferredValueבמקום זאת. -
הפונקציה שאתה מעביר ל-
startTransitionחייבת להיות סינכרונית. React מבצע מיד את הפונקציה הזו, ומסמן את כל העדכונים state שקורים בזמן ביצועה כמעברים. אם תנסה לבצע יותר עדכוני state מאוחר יותר (לדוגמה, בזמן קצוב), הם לא יסומנו כמעברים. -
עדכון state המסומן כמעבר יופסק על ידי עדכוני state אחרים. לדוגמה, אם תעדכן רכיב תרשים בתוך מעבר, אבל אז תתחיל להקליד בקלט בזמן שהתרשים נמצא באמצע רינדור מחדש, React יתחיל מחדש את עבודת העיבוד ברכיב התרשים לאחר טיפול בעדכון הקלט.
-
עדכוני מעבר לא יכולים להיות used כדי לשלוט בקלט טקסט.
-
אם יש מספר מעברים מתמשכים, React כרגע מקבץ אותם יחד. זוהי מגבלה שככל הנראה תוסר במהדורה עתידית.
שימוש
סימון עדכון state כמעבר לא חוסם
התקשר ל-useTransition ברמה העליונה של הרכיב שלך כדי לסמן עדכונים של state כמעברים שאינם חוסמים.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}useTransition מחזיר מערך עם שני פריטים בדיוק:
- הדגל
isPendingשאומר לך אם יש מעבר בהמתנה. - הפונקציה
startTransitionהמאפשרת לך לסמן עדכון state כמעבר.
לאחר מכן תוכל לסמן עדכון state כמעבר כך:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}מעברים מאפשרים לך לשמור על עדכוני ממשק user מגיבים אפילו במכשירים איטיים.
עם מעבר, ממשק המשתמש שלך נשאר מגיב באמצע עיבוד מחדש. לדוגמה, אם ה-user לוחץ על כרטיסייה אבל אז משנה את דעתו ולוחץ על כרטיסייה אחרת, הם יכולים לעשות זאת מבלי לחכות לסיום העיבוד המחודש הראשון.
Example 1 of 2: עדכון הכרטיסייה הנוכחית במעבר
בדוגמה זו, הכרטיסייה “פוסטים” האטה באופן מלאכותי כך שלוקח שנייה לפחות לעיבוד.
לחץ על “פוסטים” ולאחר מכן לחץ מיד על “צור קשר”. שימו לב שזה מפריע לעיבוד האיטי של “פוסטים”. הכרטיסייה “צור קשר” מופיעה מיד. מכיוון שעדכון state זה מסומן כמעבר, רינדור איטי מחדש לא הקפיא את ממשק user.
import { useState, useTransition } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } return ( <> <TabButton isActive={tab === 'about'} onClick={() => selectTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')} > Posts (slow) </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => selectTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </> ); }
עדכון רכיב האב במעבר
אתה יכול לעדכן את state של רכיב אב גם מהקריאה useTransition. לדוגמה, רכיב TabButton זה עוטף את ההיגיון onClick שלו במעבר:
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
}מכיוון שרכיב האב מעדכן את state שלו בתוך מטפל האירועים onClick, עדכון state יסומן כמעבר. זו הסיבה, כמו בדוגמה הקודמת, אתה יכול ללחוץ על “פוסטים” ולאחר מכן ללחוץ מיד על “צור קשר”. עדכון הכרטיסייה שנבחרה מסומן כמעבר, כך שהוא אינו חוסם אינטראקציות user.
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
הצגת חזותית ממתינה state במהלך המעבר
אתה יכול use את הערך הבוליאני isPending המוחזר על ידי useTransition כדי לציין ל-user שמתבצע מעבר. לדוגמה, לחצן הכרטיסייה יכול להיות חזותי מיוחד “בהמתנה” state:
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...שימו לב כיצד לחיצה על “פוסטים” מרגישה כעת יותר מגיבה מכיוון שכפתור הכרטיסייה עצמו מתעדכן מיד:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
מניעת מחווני טעינה לא רצויים
בדוגמה זו, הרכיב PostsTab מביא נתונים מסוימים באמצעות מקור נתונים Suspense-enabled. כאשר אתה לוחץ על הכרטיסייה “פוסטים”, הרכיב PostsTab מושעה, מה שגורם להופעת החזרת הטעינה הקרובה ביותר:
import { Suspense, useState } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [tab, setTab] = useState('about'); return ( <Suspense fallback={<h1>🌀 Loading...</h1>}> <TabButton isActive={tab === 'about'} onClick={() => setTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => setTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </Suspense> ); }
הסתרת כל מיכל הכרטיסיות כדי להציג מחוון טעינה מובילה לחוויית user צורמת. אם תוסיף את useTransition ל-TabButton, במקום זאת תוכל לציין את ה-state הממתין בלחצן הכרטיסייה במקום זאת.
שימו לב שלחיצה על “פוסטים” כבר לא מחליפה את כל מיכל הכרטיסיות בספינר:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
קרא עוד על שימוש במעברים עם Suspense.
בניית נתב תומך Suspense
אם אתה בונה מסגרת React או נתב, אנו ממליצים לסמן ניווטים בדפים כמעברים.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...זה מומלץ משתי סיבות:
- מעברים ניתנים להפסקה, המאפשר ל-user ללחוץ משם מבלי לחכות לסיום העיבוד מחדש.
- מעברים מונעים מחווני טעינה לא רצויים, המאפשר ל-user להימנע מקפיצות צורמות בניווט.
הנה דוגמה זעירה לנתב פשוטה המשתמשת במעברים לניווטים.
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>; }
הצגת שגיאה לusers עם גבול שגיאה
אם פונקציה שהועברה ל-startTransition זורקת שגיאה, תוכל להציג שגיאה ל-user שלך עם גבול שגיאה. כדי use גבול שגיאה, עטוף את הרכיב שבו אתה קורא ל-useTransition בגבול שגיאה. לאחר שהפונקציה תועבר לשגיאות startTransition, תוצג החזרה לגבול השגיאה.
import { useTransition } from "react"; import { ErrorBoundary } from "react-error-boundary"; export function AddCommentContainer() { return ( <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}> <AddCommentButton /> </ErrorBoundary> ); } function addComment(comment) { // For demonstration purposes to show Error Boundary if (comment == null) { throw new Error("Example Error: An error thrown to trigger error boundary"); } } function AddCommentButton() { const [pending, startTransition] = useTransition(); return ( <button disabled={pending} onClick={() => { startTransition(() => { // Intentionally not passing a comment // so error gets thrown addComment(); }); }} > Add comment </button> ); }
פתרון בעיות
עדכון קלט במעבר לא עובד
אתה לא יכול use מעבר עבור משתנה state השולט בקלט:
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ Can't use transitions for controlled input state
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;הסיבה לכך היא שמעבריםuse אינם חוסמים, אך עדכון קלט בתגובה לאירוע השינוי אמור להתרחש באופן סינכרוני. אם ברצונך להפעיל מעבר בתגובה להקלדה, יש לך שתי אפשרויות:
- ניתן להכריז על שני משתנים נפרדים של state: אחד עבור הקלט state (שתמיד מתעדכן באופן סינכרוני), ואחד שתעדכן במעבר. זה מאפשר לך לשלוט בקלט באמצעות ה-state הסינכרוני, ולהעביר את משתנה המעבר state (ש”ישאר אחרי” הקלט) לשאר הלוגיקה של העיבוד שלך.
- לחלופין, אתה יכול לקבל משתנה state אחד, ולהוסיף
useDeferredValueש”יפגר מאחורי” הערך האמיתי. זה יפעיל עיבוד מחדש לא חוסם כדי “להדביק” את הערך החדש באופן אוטומטי.
React לא מתייחס לעדכון state שלי כאל מעבר
כאשר אתה עוטף עדכון state במעבר, ודא שהוא קורה במהלך הקריאה startTransition:
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});הפונקציה שאתה מעביר ל-startTransition חייבת להיות סינכרונית.
לא ניתן לסמן עדכון כמעבר כך:
startTransition(() => {
// ❌ Setting state *after* startTransition call
setTimeout(() => {
setPage('/about');
}, 1000);
});במקום זאת, תוכל לעשות זאת:
setTimeout(() => {
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
}, 1000);באופן דומה, אינך יכול לסמן עדכון כמעבר כך:
startTransition(async () => {
await someAsyncFunction();
// ❌ Setting state *after* startTransition call
setPage('/about');
});עם זאת, זה עובד במקום זאת:
await someAsyncFunction();
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});אני רוצה לקרוא לuseTransition מחוץ לרכיב
אתה לא יכול לקרוא ל-useTransition מחוץ לרכיב כי use זה Hook. במקרה זה, use השיטה העצמאית startTransition במקום זאת. זה עובד באותו אופן, אבל זה לא מספק את מחוון isPending.
הפונקציה שאני מעביר לstartTransition מבצעת מיד
אם תפעיל את הקוד הזה, הוא ידפיס 1, 2, 3:
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);זה צפוי להדפיס 1, 2, 3. הפונקציה שתעביר ל-startTransition לא מתעכבת. שלא כמו בדפדפן setTimeout, הוא לא מפעיל את ההתקשרות חזרה מאוחר יותר. React מבצע את הפונקציה שלך באופן מיידי, אבל כל עדכוני state המתוזמנים בזמן שהוא פועל מסומנים כמעברים. אתה יכול לדמיין שזה עובד ככה:
// A simplified version of how React works
let isInsideTransition = false;
function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}
function setState() {
if (isInsideTransition) {
// ... schedule a transition state update ...
} else {
// ... schedule an urgent state update ...
}
}