useDeferredValue

useDeferredValue הוא React Hook המאפשר לך לדחות עדכון של חלק מהממשק.

const deferredValue = useDeferredValue(value)

הפניה

useDeferredValue(value)

התקשר ל-useDeferredValue ברמה העליונה של הרכיב שלך כדי לקבל גרסה נדחית של הערך הזה.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

ראה דוגמאות נוספות למטה.

פרמטרים

  • value: הערך שברצונך לדחות. זה יכול להיות כל סוג.

מחזירה

במהלך העיבוד הראשוני, הערך הדחוי המוחזר יהיה זהה לערך שסיפקת. במהלך עדכונים, React ינסה תחילה עיבוד מחדש עם הערך הישן (כך הוא יחזיר את הערך הישן), ולאחר מכן ינסה עיבוד מחדש ברקע עם הערך החדש (כך הוא יחזיר את הערך המעודכן).

אזהרות

  • הערכים שתעביר ל-useDeferredValue צריכים להיות ערכים פרימיטיביים (כמו מחרוזות ומספרים) או אובייקטים שנוצרו מחוץ לעיבוד. אם אתה יוצר אובייקט חדש במהלך הרינדור ומיד מעביר אותו ל-useDeferredValue, הוא יהיה שונה בכל רינדור, ויגרום לעיבוד מחדש של רקע מיותר.

  • כאשר useDeferredValue מקבל ערך שונה (בהשוואה ל-Object.is), בנוסף לעיבוד הנוכחי (כאשר הוא עדיין use הוא הערך הקודם), הוא מתזמן עיבוד מחדש ברקע עם הערך החדש. העיבוד מחדש ברקע ניתן להפסקה: אם יש עדכון נוסף לרקע __K_2 יתחיל מחדש את הרקע __K_2 scratch לדוגמה, אם ה-user מקליד בקלט מהר יותר ממה שתרשים שמקבל את הערך הנדחה שלו יכול לעבד מחדש, התרשים יוצג מחדש רק לאחר שה-user יפסיק להקליד.

  • useDeferredValue משולב עם <Suspense>. אם עדכון הרקע caused על ידי ערך חדש משעה את ממשק המשתמש, ה-user לא יראה את החזרה. הם יראו את הערך הדחוי הישן עד לטעינת הנתונים.

  • useDeferredValue אינו מונע כשלעצמו בקשות רשת נוספות.

  • אין עיכוב קבוע caused על ידי useDeferredValue עצמו. ברגע שReact מסיים את העיבוד מחדש המקורי, React יתחיל מיד לעבוד על העיבוד מחדש ברקע עם הערך הדחוי החדש. כל עדכונים caused על ידי אירועים (כמו הקלדה) יפריעו לעיבוד מחדש ברקע ויקבלו עדיפות על פניו.

  • העיבוד מחדש של הרקע caused על ידי useDeferredValue אינו מפעיל אפקטים עד שהוא מחויב למסך. אם העיבוד מחדש ברקע מושעה, האפקטים שלו יפעלו לאחר טעינת הנתונים ועדכוני ממשק המשתמש.


שימוש

מציג תוכן מיושן בזמן טעינת תוכן טרי

התקשר ל-useDeferredValue ברמה העליונה של הרכיב שלך כדי לדחות עדכון של חלק כלשהו מהממשק שלך.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

במהלך העיבוד הראשוני, הערך הדחוי יהיה זהה לערך שסיפקת.

במהלך עדכונים, הערך הדחוי “יפגר מאחורי” הערך האחרון. בפרט, React תחילה יעבד מחדש ללא לעדכן את הערך הנדחה, ולאחר מכן ינסה לעבד מחדש עם הערך החדש שהתקבל ברקע.

בואו נעבור על דוגמה כדי לראות מתי זה מלא use.

Note

דוגמה זו מניחה שאתה use מקור נתונים מאופשר Suspense:

  • אחזור נתונים עם מסגרות התומכות ב-Suspense כמו Relay ו-Next.js
  • קוד רכיב בטעינה עצלנית עם lazy
  • קריאת הערך של הבטחה עם use

למידע נוסף על Suspense והמגבלות שלו.

בדוגמה זו, הרכיב SearchResults משהה בזמן שליפת תוצאות החיפוש. נסה להקליד "a", להמתין לתוצאות, ולאחר מכן לערוך אותו ל-"ab". התוצאות עבור "a" מוחלפות ב-fallback הטעינה.

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 כדי להעביר גרסה נדחית של השאילתה:

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 יציג את התוצאות המעופשות לזמן מה.

הזן "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);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Deep Dive

איך עובדת דחיית ערך מתחת למכסה המנוע?

אתה יכול לחשוב שזה קורה בשני שלבים:

  1. ראשית, React מעבד מחדש עם query החדש ("ab") אבל עם deferredQuery הישן (עדיין "a"). הערך deferredQuery, שאתה מעביר לרשימת התוצאות, נדחה: הוא “פוגר אחרי” הערך _TK

  2. ברקע, React מנסה לעבד מחדש עם גם query וגם deferredQuery מעודכנות ל-"ab". אם העיבוד מחדש הזה יסתיים, React יציג אותו על המסך. עם זאת, אם הוא מושהה (התוצאות עבור "ab" עדיין לא נטענו), React ינטוש את ניסיון העיבוד הזה, וינסה שוב לבצע עיבוד מחדש לאחר טעינת הנתונים. ה-user ימשיך לראות את הערך הדחוי המעופש עד שהנתונים יהיו מוכנים.

עיבוד ה”רקע” הנדחה ניתן להפסקה. לדוגמה, אם תקליד שוב בקלט, React ינטוש אותו ויתחיל מחדש עם הערך החדש. React תמיד use הערך האחרון שסופק.

שימו לב שעדיין ישנה בקשת רשת בכל הקשה. מה שנדחה כאן הוא הצגת תוצאות (עד שהן מוכנות), לא בקשות הרשת עצמן. גם אם ה-user ממשיך להקליד, התגובות עבור כל הקשה על המקשים נשמרות במטמון, כך שהקשה על Backspace היא מיידית ואינה מביאה שוב.


מציין שהתוכן מיושן

בדוגמה שלמעלה, אין אינדיקציה שרשימת התוצאות עבור השאילתה האחרונה עדיין בטעינה. זה יכול לבלבל את ה-user אם התוצאות החדשות ייקח זמן מה לטעון. כדי שיהיה ברור יותר ל-user שרשימת התוצאות אינה תואמת את השאילתה העדכנית ביותר, תוכל להוסיף אינדיקציה חזותית כאשר רשימת התוצאות המיושנת מוצגת:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>

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

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,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


דחיית עיבוד מחדש עבור חלק ממשק המשתמש

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

תאר לעצמך שיש לך שדה טקסט ורכיב (כמו תרשים או רשימה ארוכה) שמוצגים מחדש בכל הקשה:

function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}

ראשית, בצע אופטימיזציה של SlowList כדי לדלג על עיבוד מחדש כאשר ה-props שלו זהים. כדי לעשות זאת, עטפו אותו ב-memo:

const SlowList = memo(function SlowList({ text }) {
// ...
});

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

באופן קונקרטי, בעיית הביצועים העיקרית היא שבכל פעם שאתה מקליד בקלט, ה-SlowList מקבל props חדש, ורינדור מחדש של כל העץ שלו גורם להקלדה להרגיש מופרכת. במקרה זה, useDeferredValue מאפשר לך לתעדף את עדכון הקלט (שחייב להיות מהיר) על פני עדכון רשימת התוצאות (אשר מותר להיות איטי יותר):

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}

זה לא הופך את העיבוד מחדש של ה-SlowList למהיר יותר. עם זאת, הוא אומר ל-React שניתן לבטל את סדר העדיפויות של עיבוד מחדש של הרשימה כך שלא יחסום את ההקשות. הרשימה “תעכב מאחורי” הקלט ולאחר מכן “תדביק”. כמו קודם, React ינסה לעדכן את הרשימה בהקדם האפשרי, אך לא יחסום את user מהקלדה.

The difference between useDeferredValue and unoptimized re-rendering

Example 1 of 2:
עיבוד מחדש נדחה של הרשימה

בדוגמה זו, כל פריט ברכיב SlowList מואט באופן מלאכותי** כך שתוכל לראות כיצד useDeferredValue מאפשר לך לשמור על הקלט תגובה. הקלד את הקלט ושם לב שההקלדה מרגישה מהירה בזמן שהרשימה “נגררת” אחריה.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Pitfall

אופטימיזציה זו מחייבת את SlowList לעטוף ב-memo. זה בגלל use בכל פעם שה-text משתנה, React צריך להיות מסוגל לעבד מחדש את רכיב האב במהירות. במהלך העיבוד מחדש, ל-deferredText עדיין יש את הערך הקודם שלו, כך ש-SlowList מסוגל לדלג על רינדור מחדש (props שלו לא השתנה). ללא memo, הוא יצטרך בכל מקרה לעבד מחדש, ולהביס את נקודת האופטימיזציה.

Deep Dive

במה שונה דחיית ערך מהקפצה ומצערת?

ישנן שתי טכניקות אופטימיזציה נפוצות שעשויות להיות לך used בעבר בתרחיש זה:

  • הקפצה פירושה שתמתין עד שה-user יפסיק להקליד (למשל לשנייה) לפני שתעדכן את הרשימה.
  • מחסנת פירושה שתעדכן את הרשימה מדי פעם (למשל, לכל היותר פעם בשנייה).

בעוד שטכניקות אלו מועילות במקרים מסוימים, useDeferredValue מתאימה יותר לאופטימיזציה של רינדור מכיוון שהיא משולבת עמוקות עם React עצמה ומסתגלת למכשיר של user.

בניגוד להקפצה או מצערת, זה לא מצריך בחירת השהיה קבועה כלשהי. אם המכשיר של ה-user מהיר (לדוגמה, מחשב נייד חזק), הרינדור החוזר הנדחה יתרחש כמעט מיד ולא יהיה מורגש. אם המכשיר של ה-user איטי, הרשימה “תפגר מאחורי” הקלט באופן פרופורציונלי לאיטי המכשיר.

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

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