Menu

ההוק useTransition ב-React: ממשק שנשאר רספונסיבי

useTransition מסמן עדכון state כלא דחוף, כך ש-React יכולה לשמור על הדף רספונסיבי בזמן שהיא מרנדרת את החלק האיטי ברקע. למדו את isPending, את startTransition, transitions אסינכרוניים ב-React 19, ולמה זה לא debounce.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

useTransition מאפשר לסמן עדכון state כלא דחוף. React מרנדרת את העדכון הזה ברקע וזונחת או מתחילה אותו מחדש אם קורה משהו דחוף יותר, כמו לחיצה או הקשה, כך שהדף נשאר רספונסיבי בזמן שחלק איטי במסך משיג את הקצב.

לחצו על posts ואז, לפני שהרשימה מופיעה, לחצו על contact. Contact מופיע מיד: React זרקה את רינדור ה-posts שהיה באמצע. עכשיו שנו את startTransition(() => { setTab(next); }) ל-setTab(next); רגיל ונסו את אותו דבר: הדף קופא לכ-300 מילישניות אחרי הלחיצה על posts, והלחיצה שלכם על contact מחכה עד שה-posts מסתיימים.

התחביר

const [isPending, startTransition] = useTransition();

startTransition(() => {
    setTab(next);
});
  • startTransition מקבל פונקציה ומריץ אותה מיד. כל עדכוני ה-state שבתוכה מסומנים כ-transitions.
  • isPending הוא true מהרגע שמתחילים את ה-transition ועד ש-React סיימה לרנדר אותו ועשתה commit לתוצאה.

useTransition לא מקבל ארגומנטים. הפונקציה שמעבירים רצה באופן סינכרוני; ה-transition נוגע לאופן שבו React מרנדרת את העדכון שנוצר, לא למתי הפונקציה שלכם רצה.

מה פירוש "לא דחוף"

React מחלקת עדכונים לשני סוגים. עדכונים דחופים, כמו הקלדה, לחיצה ולחיצה על מקש, צריכים להשתקף מיד אחרת הדף מרגיש שבור. עדכוני transition, כמו מעבר לתצוגה חדשה או הצגת תוצאות מסוננות, יכולים לקחת רגע.

React מרנדרת transition בפרוסות קטנות ובודקת בין קומפוננטות אם הגיע משהו דחוף. אם כן, React עוצרת או נוטשת את ה-transition, מטפלת בעדכון הדחוף, ואז מתחילה את ה-transition שוב עם ה-state העדכני. בזמן ש-transition מתרנדר, המסך הקודם נשאר; React לא מציגה תוצאה חצי בנויה. לכן הדוגמה ממשיכה להציג את טקסט ה-about, עם רמז ה-Loading ליד הכפתורים, עד שה-posts מוכנים.

קטיעה קורית בין קומפוננטות. קומפוננטה בודדת שחוסמת ל-300 מילישניות לא ניתנת לחיתוך באמצע, ולכן העבודה האיטית שלמעלה מפוזרת על 300 קומפוננטות SlowPost קטנות.

הצגת רמז pending

isPending הוא המקום לאינדיקטור טעינה שלא מחליף את התוכן הנוכחי: ספינר ליד הכפתור, טקסט מעומעם, תווית "Saving...". בדוגמה הראשונה הוא מציג Loading... ליד הטאבים. אפשר להשתמש בו גם כדי לעצב את התוכן:

<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>

סינון רשימה גדולה תוך כדי הקלדה

אי אפשר לשים את הערך של שדה עצמו ב-transition. <input> מבוקר חייב לקבל את הערך החדש שלו באותו עדכון דחוף, אחרת תווים הולכים לאיבוד. החזיקו במקום שני חלקי state: הטקסט של השדה (דחוף) והשאילתה שהרשימה משתמשת בה (transition).

הקלידו 123 מהר. השדה אף פעם לא מפגר. ה-Console עדיין רושם שורת rendering results אחת לכל הקשה, כי Results רושמת ברגע ש-React מתחילה לרנדר אותה, אבל הרשימות של 1 ו-12 אף פעם לא מגיעות למסך: כל הקשה חדשה יותר קטעה את הרינדור שהיה באמצע, ו-React התחילה שוב עם השאילתה העדכנית. Results עטופה ב-memo בכוונה: בלעדיו, הרינדור הדחוף של כל הקשה היה מרנדר גם את הרשימה האיטית עם השאילתה הישנה, והייתם מאבדים את התועלת.

startTransition בלי ההוק

react מייצאת גם את startTransition כפונקציה רגילה. היא מסמנת עדכונים כ-transitions בדיוק כמו ההוק, אבל לא מקבלים isPending. השתמשו בה במקומות שבהם אי אפשר לקרוא להוק, למשל בספריית נתונים או בראוטר מחוץ לכל קומפוננטה.

import { startTransition } from 'react';

function navigate(url) {
    startTransition(() => {
        setCurrentUrl(url);
    });
}

transitions אסינכרוניים ב-React 19

מאז React 19 הפונקציה שמעבירים יכולה להיות async. isPending נשאר אז true לכל אורך הזמן שהפונקציה רצה, כולל ההמתנה, מה שהופך אותו לדרך פשוטה להציג מצב pending בזמן בקשה. ה-saveName המזויף שמתחת מחליף בקשה אמיתית.

שימו לב ל-startTransition השני סביב setSaved. React מאבדת את המעקב אחרי ה-transition מעבר ל-await, ולכן עדכון state אחריו צריך להיעטף שוב כדי להיחשב כחלק מה-transition. React קוראת לפונקציה שמועברת בצורה כזו action; useActionState ו-form actions בנויים על אותו מנגנון.

transition הוא לא debounce

debounce מחכה פרק זמן קבוע אחרי ההקשה האחרונה לפני שהוא עושה משהו. transition לא מחכה. הוא מתחיל לרנדר מיד, ואם מגיע עדכון חדש יותר הוא זונח את העבודה הישנה. במכשיר מהיר התוצאה מופיעה בלי שום השהיה; במכשיר איטי, React עושה כמה שהיא יכולה בין הקשות. מה ש-transition לא יכול לעשות זה להפחית את מספר בקשות הרשת: אם כל הקשה מתחילה fetch, עשו debounce ל-fetch.

transitions ו-Suspense

transitions גם משנים את מה שגבול Suspense עושה. בדרך כלל, כשקומפוננטה בתוך גבול עושה suspend (קומפוננטה lazy שנטענת, או נתונים שנקראים עם use), React מחליפה את התוכן ב-fallback. אם העדכון שגרם ל-suspend היה transition, React שומרת במקום זאת את התוכן שכבר על המסך, ו-isPending נשאר true עד שהתוכן החדש מוכן. כך ראוטרים נמנעים מלהבהב ספינר בגודל דף בכל ניווט: הם עוטפים את הניווט ב-transition, והדף הישן נשאר גלוי עד שהבא יכול להתרנדר. תוכן שאף פעם לא הוצג עדיין משתמש ב-fallback, כך שגבול שנחשף לראשונה מתנהג כרגיל.

דברים שלא עובדים

  • עדכונים בתוך timeout. startTransition(() => setTimeout(() => setTab(next), 100)) לא מסמן את העדכון, כי הוא רץ אחרי ש-startTransition כבר חזר. שימו את startTransition בתוך ה-timeout במקום.
  • קומפוננטה איטית בודדת. React יכולה לקטוע רק בין קומפוננטות, ולכן קומפוננטה אחת שחוסמת לאורך זמן עדיין חוסמת.
  • שליטה בשדה קלט. כמו שלמעלה, ה-state של השדה עצמו נשאר דחוף.

למקרה שבו ה-setter של ה-state לא שלכם ואתם רק מקבלים ערך, כמו prop, useDeferredValue נותן לכם את אותה התנהגות.

שאלות נפוצות

מה useTransition עושה?

הוא נותן לכם את startTransition, שמסמן את עדכוני ה-state שבתוכו כלא דחופים, ואת isPending, שהוא true כל עוד העדכון הזה עדיין מתרנדר. React יכולה לקטוע רינדור לא דחוף כדי לטפל קודם בלחיצות ובהקלדה.

מה ההבדל בין useTransition ל-startTransition?

שניהם מסמנים עדכונים כ-transitions. ההוק useTransition נותן לכם גם את isPending בשביל רמז טעינה. ה-startTransition העצמאי שמייבאים מ-react עובד מחוץ לקומפוננטות אבל אין לו דגל pending.

אפשר להשתמש ב-useTransition בשדה טקסט?

לא בשביל הערך של השדה עצמו. שדה מבוקר חייב להתעדכן באופן סינכרוני אחרת ההקלדה נשברת. השאירו את ה-state של השדה דחוף ושימו את העדכון היקר שתלוי בו ב-transition, או השתמשו ב-useDeferredValue.

האם useTransition זה כמו debounce?

לא. debounce מחכה זמן קבוע לפני שהוא עושה משהו. transition מתחיל לרנדר מיד ונקטע אם מגיע עדכון חדש יותר, כך שבמכשיר מהיר התוצאה מופיעה בלי שום השהיה.

האם startTransition יכול לקבל פונקציה async?

כן, מאז React 19. isPending נשאר true עד שהפונקציה האסינכרונית מסתיימת. עדכוני state אחרי await חייבים להיעטף בקריאה נוספת ל-startTransition כדי להיות מסומנים כ-transition.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל