הפקודות debounce ו deferred בריאקט
בואו נדבר על שני מנגנונים פופולריים ביישומי ריאקט עם סינון רשימות ונבין מה ההבדל ביניהם ומתי נשתמש בכל אחד (או בשניהם יחד).
1. עדכון עם Debounce בריאקט
כשיש לי רשימה גדולה של פריטים ואני רוצה לסנן אותה הסינון עצמו עלול להיות פעולה כבדה. יותר מזה, אם הסינון עצמו כולל גישה לשרת הפעלת יותר מדי פעולות סינון יכולה ליצור עומס על השרת. לכן אנחנו משתמשים במנגנון debounce כדי לייצב את הקריאות, כלומר נרצה לשלוח את בקשת הסינון לשרת רק אחרי שהמשתמש סיים רצף הקלדות. בדרך כלל debounce ממומש על ידי המתנה של חצי שניה או שניה מרגע השינוי האחרון ועד לרגע שבאמת שולחים את הודעת החיפוש לשרת. אם במהלך הזמן הזה המשתמש מקליד משהו נוסף אז נתחיל את הספירה מחדש.
הפונקציה useDebounce
מתוך האוסף useHooks ממומשת כך:
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
היא לוקחת ערך שהוא לרוב משתנה סטייט כלשהו ומספר מילי שניות להמתנה. היא מגדירה משתנה סטייט פנימי נוסף שיתעדכן רק אחרי שהערך המקורי לא השתנה במשך delay מילי-שניות. בעזרת אפקט הפונקציה מגדירה timeout ומאפסת את השעון כל פעם ש value משתנה.
אני משתמש בפונקציה באופן הבא:
function TextWithDebounce() {
const [text, setText] = useState('');
const debouncedText = useDebounce(text, 1000);
return (
<>
<input type="text" value={text} onChange={e => setText(e.target.value)} />
<p>Character Count: {debouncedText.length}</p>
</>
)
}
הקומפוננטה תציג תיבת טקסט ורק אחרי שמשתמש מפסיק לעדכן את הטקסט בתיבה היא תציג את מספר התווים שהוקלדו.
2. עדכון עם useDeferredValue בריאקט
הפונקציה useDeferredValue
היא כבר חלק מובנה בריאקט ומטרתה לייצב את ממשק המשתמש באמצעות הרצת קוד ריאקט ברקע. אני אסביר:
כשריאקט מזהה שינוי במשתנה סטייט הוא מיד מריץ את פונקציית הקומפוננטה בה נמצא אותו משתנה סטייט, ובעקבותיה ממשיך לחשב את כל הקומפוננטות שבתוכה, כדי לבנות את ה Virtual DOM החדש שמושפע מהשינוי.
הרצת תהליך החישוב עשויה להיות איטית, ובזמן הזה ממשק המשתמש לא מעודכן. יותר מזה, במהלך תהליך החישוב יכולים לקרות אירועי UI נוספים שיטופלו באיחור כי הדפדפן עסוק בחישוב ה Virtual DOM של השינוי הקודם.
פונקציית useDeferredValue מציעה פתח מילוט מבעיה זו - היא מאפשרת לנו להמשיך להציג את הערך הישן ולחשב את ה Virtual DOM החדש ברקע. אם יהיו אירועי UI נוספים שישנו את הסטייט ריאקט יוכל לעצור את החישוב ברקע ולהתחיל חישוב מחדש עם הערך החדש, כל זאת בלי לפגוע במידע שמוצג על המסך. רק אחרי שהחישוב ברקע יסתיים ריאקט יחליף את התוכן שעל המסך "במכה אחת" בלי לפגוע בחווית המשתמש.
נתבונן בדוגמת הקוד הבאה מדף התיעוד של הפונקציה:
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} />
</>
);
}
בהנחה שלוקח הרבה זמן לבנות את SlowList עבור טקסט מסוים, השימוש ב deferredValue מאפשר לריאקט להגיב לשינויים ב text מיד כשהם קורים ולהריץ את החישוב של SlowList ברקע. אם החישוב מסתיים ו text לא השתנה אז במכה אחת הערך של deferredText יוחלף לערך החדש של text וריאקט ישתמש ב Virtual DOM שהוא כבר חישב. אם text משתנה באמצע החישוב אז ריאקט פשוט יעצור את החישוב הקודם ויתחיל לחשב מחדש את SlowList עם הטקסט החדש.
נ.ב. בשביל שדוגמה זו תעבוד SlowList חייב להיות מוגדר עם memo כי כש text משתנה ריאקט מנסה לחשב מחדש את App ובלי memo גם אם deferredText לא משתנה עדיין ריאקט יצטרך לחשב מחדש את SlowList.
בדוגמאות של ריאקט באותו עמוד הם מראים איך להשתמש ב useDeferredValue גם לעבודה עם בקשות רשת, אבל פה צריך להדגיש שמנגנון זה לא מווסת את כמות הקריאות לשרת אלא רק את עדכוני ממשק המשתמש ולכן לא מספיק לבדו כדי לווסת קוד תקשורת.