עיון ב-Hooks API
Hooks הם תוספת חדשה ב-React 16.8. הם נותנים לנו להשתמש ב-state ופיצ’רים אחרים של React מבלי לכתוב מחלקה.
עמוד זה מתאר את ה-APIs של ה-Hooks המובנים בתוך React.
אם הנושא של Hooks חדש לך, יכול להיות שתרצה לקרוא את הסקירה הכללית קודם. יכול להיות שתמצא מידע שימושי בסעיף שאלות נפוצות.
Hooks בסיסיים
useState
const [state, setState] = useState(initialState);
מחזיר ערך stateful, ופונקציה על מנת לעדכן אותו.
בזמן הרינדור הראשוני, ה-state המוחזר (state
) הוא שווה ערך לערך המועבר כארגומנט הראשון (initialState
).
פונקציית ה-setState
משמשת לעדכון ה-state. היא מקבלת ערך state חדש וקובעת רינדור מחדש של הקומפוננטה.
setState(newState);
בזמן הרינדורים העוקבים, הערך הראשון שמוחזר על ידי useState
תמיד יהיה ה-state האחרון לאחר יישום העדכונים.
הערה
React מבטיח שזהות פונקציית ה-
setState
יציבה ולא תשתנה בין רינדורים. זאת הסיבה שזה בטוח להשמיט
עדכונים פונקציונליים
אם ה-state החדש חושב באמצעות ה-state הקודם, ניתן להעביר פונקציה ל-setState
. הפונקציה תקבל את הערך הקודם, ותחזיר ערך מעודכן. הנה דוגמה של קומפוננטת counter שמשתמשת בשתי הצורות של setState
:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
כפתורי ה-”+” וה-”-” משתמשים בצורה הפונקציונלית, בגלל שהערך המעודכן מבוסס על הערך הקודם. אבל כפתור ה”Reset” משתמש בצורה הרגילה, בגלל שהוא תמיד מעדכן את הספירה חזרה לערך ההתחלתי.
הערה אם הפונקציה מחזירה ערך שווה לזה שקיים ב-state הנוכחי, הרינדור הבא ידולג לגמרי.
בשונה ממתודת ה-
setState
שנמצאת בקומפוננטות מחלקה,useState
לא ממזגת עדכוני אובייקטים באופן אוטומטי. ניתן לחקות התנהגות זו על ידי שילוב של מעדכן פונקציה עם אופן הכתיבה של object spread(שלוש נקודות ’…’):const [state, setState] = useState({}); setState(prevState => { // Object.assign גם יעבוד return {...prevState, ...updatedValues}; });
אופציה נוספת היא
useReducer
, שמתאים יותר לניהול אובייקטי(objects) state שמכילים מספר רב של תת-ערכים.
Initial state עצלן
הארגומנט initialState
הוא ה-state שהשתמשנו בו ברינדור הראשון. ברינדורים עוקבים, מתעלמים ממנו. אם ה-state ההתחלתי הוא התוצאה של חישוב יקר, ניתן לספק פונקציה במקום, שתרוץ רק ברינדור ההתחלתי:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
יציאה מעדכון state
אם אתה מעדכן State Hook לערך ששווה לערך הנוכחי, React יצא מהפעולה מבלי רינדור הילדים או יריית אפקטים. (React משתמש באלגוריתם ההשוואה Object.is
.)
שים לב שיכול להיות ש-React יצטרך לרנדר את הקומפוננטה הספציפית הזו לפני יציאה מהפעולה. זה לא אמור להיות מדאיג בגלל ש-React לא ילך שלא כצורך “עמוק” לתוך העץ. אם אתה מבצע חישובים יקרים בזמן רינדור, ניתן למטב אותם עם useMemo
.
Batching of state updates
React may group several state updates into a single re-render to improve performance. Normally, this improves performance and shouldn’t affect your application’s behavior.
Before React 18, only updates inside React event handlers were batched. Starting with React 18, batching is enabled for all updates by default. Note that React makes sure that updates from several different user-initiated events — for example, clicking a button twice — are always processed separately and do not get batched. This prevents logical mistakes.
In the rare case that you need to force the DOM update to be applied synchronously, you may wrap it in flushSync
. However, this can hurt performance so do this only where needed.
useEffect
useEffect(didUpdate);
מקבלת פונקציה שמכילה קוד חיוני, שכנראה גורם לאפקט כלשהו.
Mutations, subscriptions, טיימרים, לוגים, ותופעות לוואי אחרים לא מורשים בתוך ה-main body של קומפוננטת פונקציה (המכונה שלב הרינדור של React). אי ציות לכך יגרום לבאגים מבלבלים ואי עקביות בממשק המשתמש.
במקום זאת, השתמש ב-useEffect
. הפונקציה המועברת ל-useEffect
תרוץ אחרי שהרינדור מופיע על המסך. ניתן לחשוב על אפקטים כפתח מילוט מהעולם הפונקציונלי של React לתוך העולם האימפרטיבי.
כברירת מחדל, אפקטים רצים אחרי כל רינדור שמסתיים, אבל ניתן לבחור להריץ אותם רק כשערכים מסוימים שונו.
ניקוי אפקט
לעיתים קרובות, אפקטים יוצרים משאבים שדורשים ניקוי לפני שהקומפוננטה עוזבת את המסך, כמו subscription או timer ID. על מנת לעשות זאת, הפונקציה המועברת ל-useEffect
תחזיר פונקציית נקיון. לדוגמה, על מנת ליצור subscription:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// ניקוי ה-subscription
subscription.unsubscribe();
};
});
פונקציית הנקיון רצה לפני הסרת הקומפוננטה מממשק המשתמש על מנת למנוע דליפות זיכרון. בנוסף לכך, אם קומפוננטה מתרנדרת מספר רב של פעמים (כמו שבדרך כלל קורה), האפקט הקודם מנוקה לפני הרצת האפקט הבא. בדוגמה שלנו, זה אומר ש-subscription חדש נוצר בכל עדכון. על מנת להימנע מיריית אפקט על כל עדכון, קרא את החלק הבא.
תזמון אפקטים
בשונה מ-componentDidMount
ו-componentDidUpdate
, הפונקציה שמועברת ל-useEffect
נורה לאחר פריסה וצביעה(layout and paint), בזמן אירוע נדחה. זה עושה את זה מתאים להרבה תופעות לוואי, כמו הכנת subscriptions ו- event handlers, בגלל שרוב סוגי העבודה לא חוסמים את הדפדפן מלעדכן את המסך.
למרות זאת, לא ניתן לעכב את כל האפקטים. לדוגמה, מוטציית DOM שגלויה למשתמש צריכה להיות נורה באופן סינכרוני לפני הצבע הבא כך שהמשתמש לא יבחין בחוסר עקביות חזותי. (ההבחנה דומה מבחינה קונספטואלית למאזינים לאירועים פסיביים לעומת פעילים). בשביל סוגי האפקטים האלה, React מספק Hook נוסף שנקרא useLayoutEffect
. יש לו את אותה חתימה כ-useEffect
, ושונה ממנו כשהוא נורה.
<<<<<<< HEAD
אף על פי ש-useEffect
מתעכב על שהדפדפן נצבע, זה מובטח שהוא נורה לפני רינדורים חדשים. React תמיד ינקה אפקטים של רינדורים קודמים לפני החלת עדכון חדש.
=======
Additionally, starting in React 18, the function passed to useEffect
will fire synchronously before layout and paint when it’s the result of a discrete user input such as a click, or when it’s the result of an update wrapped in flushSync
. This behavior allows the result of the effect to be observed by the event system, or by the caller of flushSync
.
Note
This only affects the timing of when the function passed to
useEffect
is called - updates scheduled inside these effects are still deferred. This is different thanuseLayoutEffect
, which fires the function and processes the updates inside of it immediately.
Even in cases where useEffect
is deferred until after the browser has painted, it’s guaranteed to fire before any new renders. React will always flush a previous render’s effects before starting a new update.
84ad3308338e2bb819f4f24fa8e9dfeeffaa970b
יריית אפקט לפי תנאי
ההתנהגות הרגילה של אפקטים היא לירות את האפקט לאחר כל רינדור שהושלם. בדרך זו אפקט תמיד נוצר מחדש אם אחד מה-dependencies שלו משתנה.
למרות זאת, זה יכול להיות יותר מדי במקרים מסוימים, כמו דוגמת ה-subscription מהקטע הקודם. אנחנו לא צריכים ליצור subscription חדש על כל עדכון, רק אם ה-prop source
שונה.
על מנת ליישם זאת, העבר ארגומנט שני ל-useEffect
שהוא מערך של ערכים שהאפקט תלוי בהם. הדוגמה המעודכנת שלנו נראית כמו זה:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
עכשיו ה-subscription ייווצר מחדש רק כש-props.source
משתנה.
הערה
אם אתה משתמש באופטימיזציה זו, וודא כי המערך מכיל את כל הערכים מ-scope הקומפוננטה (כמו props ו-state) שמשתנים לאורך זמן ושהאפקט משתמש בהם. אחרת, הקוד שלך יתייחס לערכים ישנים מרינדורים קודמים. למד עוד על על איך לטפל בפונקציות ומה לעשות כשערכי המערך משתנים בתדירות גבוהה מדי.
אם אתה רוצה להריץ אפקט ולנקות אותו רק פעם אחת (ב-mount ו-unmount), תוכל להעביר מערך ריק ( [] ) כארגומנט שני. זה אומר ל-React שהאפקט שלך לא תלוי בשום ערך מה-props או state, כך שהוא לא צריך לרוץ מחדש. זה לא מטופל כמקרה מיוחד – זה עובד כמו שמערך ה-dependencies תמיד עובד.
אם אתה מעביר מערך ריק ( [] ), ה-props ו-state בתוך האפקט תמיד יכילו את הערכים ההתחלתיים שלהם. בזמן שהעברת
[]
כארגומנט שני יותר קרוב ל-componentDidMount
ו-componentWillUnmount
כמודל מנטלי, יש פתרונות טובים יותר שעוזרים להימנע מהרצה מחדש של אפקטים בתדירות גבוהה מדי. בנוסף, אסור לשכוח ש-React מעכב הרצה שלuseEffect
עד לאחר שהדפדפן נצבע, אז עשיית עבודה נוספת היא פחות בעיה.אנו ממליצים על שימוש בחוק
exhaustive-deps
כחלק מחבילת ה-eslint-plugin-react-hooks
שלנו. הוא מזהיר מפני dependencies שמוגדרים לא נכון ומציע תיקון.
מערך ה-dependencies לא מועבר כארגומנטים לפונקציית האפקט. אבל באופן עקרוני, זה מה שהם מייצגים: כל ערך שמצוין בתוך פונקציית האפקט צריך להופיע במערך ה-dependencies. בעתיד, קומפיילר מתקדם יוכל ליצור את המערך באופן אוטומטי.
useContext
const value = useContext(MyContext);
מקבל אובייקט context (הערך המוחזר מ-React.createContext
) ומחזיר את ערך ה-context הנוכחי לאותו context. ערך ה-context הנוכחי נקבע על ידי ה-prop value
של <MyContext.Provider
מעל הקומפוננטה הקוראת בעץ.
כש-<MyContext.Provider>
מעל הקומפוננטה מתעדכן, ה-Hook מפעיל מרנדר עם value
האחרון של ה-context, ואותו ערך מועבר ל- MyContext
Provider.
אל תשכח שהארגומנט של useContext
צריך להיות אובייקט ה-context עצמו:
נכון: useContext(MyContext)
לא נכון: useContext(MyContext.Consumer)
לא נכון: useContext(MyContext.Provider)
קומפוננטה שקוראת ל-useContext
תמיד תתרנדר מחדש כשערך ה-context ישתנה. אם רינדור מחדש של הקומפוננטה הוא יקר, ניתן למטב אותו על ידי שימוש ב-memoization.
טיפ
אם אתה מכיר את ה-context API לפני Hooks,
useContext(MyContext)
הוא שווה ל-static contextType = MyContext
במחלקה, או ל-<MyContext.Consumer>
.
useContext(MyContext)
נותן לנו רק לקרוא את ה-context ולעשות subscribe לשינויים שלו. נצטרך עדיין<MyContext.Provider>
מעל בעץ על מנת לספק את הערך ל-context זה.
Hooks נוספים
ה-Hooks הבאים הם או צורות אחרות של הבסיסיים מהסעיף הקודם, או כאלה שנצטרך רק במקרי קצה ספציפיים. לא צריך להילחץ מללמוד אותם בהתחלה.
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
אלטרנטיבה ל-useState
. מקבל reducer מסוג (state, action) => newState
, ומחזיר את ה-state הנוכחי ביחד עם מתודת dispatch
. (אם התעסקת בעבר עם Redux, זה כבר מוכר לך).
בדרך כלל useReducer
עדיף על useState
כשיש לך לוגיקת state מורכבת שמערבת מספר רב של תת-ערכים או כשה-state הבא תלוי ב-state הקודם. useReducer
גם נותן לנו למטב ביצועים לקומפוננטות שמפעילות עדכונים עמוקים בגלל שניתן להעביר את dispatch מטה במקום callbacks.
הנה דוגמת ה-counter מהקטע הקודם על useState
, נכתב מחדש עם שימוש ב-reducer:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
הערה
React מבטיח שזהות פונקציית
dispatch
היא קבועה ולא תשתנה ברינדורים חוזרים. זאת הסיבה שזה בטוח להשמיט מרשימת ה-dependency שלuseEffect
אוuseCallback
.
ציון ה-state ההתחלתי
ישנם שתי דרכים שונות לאתחל useReducer
state. ניתן לבחור אחד מהם תלוי בשימוש. הדרך הפשוטה ביותר היא להעביר את ה-state ההתחלתי כארגומנט שני:
const [state, dispatch] = useReducer(
reducer,
{count: initialCount} );
הערה
React לא משתמש במוסכמת הארגומנט
state = intialState
בניגוד ל-Redux. הערך ההתחלתי לפעמים תלוי ב-props ומכאן שהוא מוגדר בקריאת ה-Hook. אם זה לא לטעמך, ניתן לקרוא ל-useReducer(reducer, undefined, reducer)
על מנת לחקות את התנהגות Redux, אבל זה לא מומלץ.
אתחול עצלן
ניתן גם ליצור state התחלתי בעצלתיים. על מנת לעשות זאת, ניתן להעביר פונקציית init
כארגומנט שלישי. ה-state ההתחלתי ייקבע ל-init(initialArg)
.
זה נותן לנו לחלץ את הלוגיקה לחישוב ה-state ההתחלתי מחוץ ל-reducer. זה גם שימושי לאיפוס ה-state לאחר מכן כתגובה לפעולה(action):
function init(initialCount) { return {count: initialCount};}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset': return init(action.payload); default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init); return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
יציאה מ-dispatch
אם אתה מעדכן State Hook לערך ששווה לערך הנוכחי, React יצא מהפעולה מבלי רינדור הילדים או יריית אפקטים. (React משתמש באלגוריתם ההשוואה Object.is
.)
שים לב שיכול להיות ש-React יצטרך לרנדר את הקומפוננטה הספציפית הזו לפני יציאה מהפעולה. זה לא אמור להיות מדאיג בגלל ש-React לא ילך שלא כצורך “עמוק” לתוך העץ. אם אתה מבצע חישובים יקרים בזמן רינדור, ניתן למטב אותם עם useMemo
.
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
מחזיר memoized callback.
העבר callback ומערך של dependencies. useCallback
תחזיר גרסה memorized של ה-callback שמשתנה רק אם אחד מה-dependencies משתנה. זה שימושי כשמעבירים callbacks לקומפוננטות ילדים ממוטבות שמסתמכות על השוואה לפי אזכור על מנת למנוע רינדורים מיותרים (לדוגמה shouldComponentUpdate
).
useCallback(fn, deps)
שווה ל-useMemo(() => fn, deps)
.
הערה
מערך ה-dependencies לא מועבר כארגומנטים לפונקציית האפקט. אבל באופן עקרוני, זה מה שהם מייצגים: כל ערך שמצוין בתוך פונקציית האפקט צריך להופיע במערך ה-dependencies. בעתיד, קומפיילר מתקדם יוכל ליצור את המערך באופן אוטומטי.
אנו ממליצים על שימוש בחוק
exhaustive-deps
כחלק מחבילת ה-eslint-plugin-react-hooks
שלנו. הוא מזהיר מפני dependencies שמוגדרים לא נכון ומציע תיקון.
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
מחזיר ערך memoized.
העבר פונקציית “create” ומערך של dependencies. useMemo
תחשב מחדש רק את הערך ה-memoized כשאחד מה-dependencies שונה. מיטוב זה עוזר להימנע מחישובים יקרים בכל רינדור.
זכור כי הפונקציה שמועברת ל-useMemo
רצה בזמן רינדור. אל תעשה דברים בתוכה שלא היית עושה בדרך כלל בזמן רינדור. לדוגמה, side effects שייכים ל-useEffect
, לא useMemo
.
אם סופק מערך כלשהו, ערך חדש יחושב בכל רינדור.
ניתן להסתמך על useMemo
כמיטוב ביצועים, לא כערבות סמנטית. בעתיד, יכול להיות ש-React יבחר “לשכוח” חלק מהערכים ה-memoized ויחשב אותם מחדש ברינדור הבא, לדוגמה, על מנת לשחרר זיכרון לקומפוננטות offscreen. כתוב את הקוד שלך כך שהוא יעבוד בלי useMemo
— ואז תוסיף אותו על מנת למטב ביצועים.
הערה
מערך ה-dependencies לא מועבר כארגומנטים לפונקציית האפקט. אבל באופן עקרוני, זה מה שהם מייצגים: כל ערך שמצוין בתוך פונקציית האפקט צריך להופיע במערך ה-dependencies. בעתיד, קומפיילר מתקדם יוכל ליצור את המערך באופן אוטומטי.
אנו ממליצים על שימוש בחוק
exhaustive-deps
כחלק מחבילת ה-eslint-plugin-react-hooks
שלנו. הוא מזהיר מפני dependencies שמוגדרים לא נכון ומציע תיקון.
useRef
const refContainer = useRef(initialValue);
useRef
מחזיר אובייקט ref שניתן לשינוי שמאפיין ה-.current
שלו מאותחל לארגומנט המועבר (intialValue
). האובייקט המוחזר יתמיד לכל מחזור החיים של הקומפוננטה.
מקרה שימוש נפוץ הוא לגשת לילד כשרוצים:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` מצביע על אלמנט ה-text input
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
במהותו, useRef
הוא כמו “קופסה” שיכולה להחזיק ערך שניתן לשינוי בתוך מאפיין ה-.current
.
אולי אתה מכיר refs בעיקר כדרך לגשת ל-DOM. אם אתה מעביר אובייקט ref ל-React עם <div ref={myRef}
, React יקבע את מאפיין ה-.current
ל-DOM node המקביל כשאותו node משתנה.
למרות זאת, useRef()
שימושי ליותר מתכונת ה-ref
. הוא שימושי לשמירת כל ערך שניתן לשינוי בדומה לדרך שהיית משתמש ב-instance fields במחלקות.
זה עובד בגלל שuseRef()
יוצר אובייקט JavaScript פשוט. ההבדל היחיד בין useRef()
ויצירת אובייקט {current: …}
בעצמך היא ש-useRef()
ייתן לך את אותו אובייקט ref בכל רינדור.
זכור ש-useRef()
לא מודיע לך כשהתוכן שלו משתנה. שינוי של המאפיין .current
לא גורם לרינדור מחדש. אם אתה רוצה להריץ קוד כש-React מצרף או מנתק ref מ-DOM node, אולי תרצה להשתמש ב-callback ref במקום.
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
מתאים אישית את הערך ה-instance שנחשף לקומפוננטות הורה בשימוש ref
. כמו תמיד, כדאי להימנע מקוד אימפרטיבי בשימוש refs ברוב המקרים. כדאי להשתמש ב-useImperativeHandle
עם forwardRef
:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
בדוגמה זו, קומפוננטת הורה שמרנדרת <FancyInput ref={fancyInputRef} />
צריכה להיות מסוגלת לקרוא ל-fancyInputRef.current.focus()
.
useLayoutEffect
מאפיין זה זהה ל-useEffect
, אבל הוא יורה באופן סינכרוני לאחר כל שינויי DOM. השתמש בזה על מנת לקרוא layout מתוך ה-DOM ולרנדר מחדש באופן סינכרוני. עדכונים מתוזמנים בתוך useLayoutEffect
ישטפו באופן סינכרוני, לפני שלדפדפן יש הזדמנות לצבוע.
העדף את useEffect
הסטנדרטי מתי שאפשר על מנת להימנע מחסימת עדכונים ויזואליים.
טיפ
אם אתה מזיז קוד מקומפוננטת מחלקה, שים לב ש-
useLayoutEffect
יורה באותו קצב כמוcomponentDidMount
ו-componentDidUpdate
. לעומת זאת, אנו ממליצים להתחיל עםuseEffect
קודם ולנסות אתuseLayoutEffect
רק אם זה יוצר בעיה.אם אתה משתמש ב-server rendering, שים לב שגם
useLayoutEffect
וגםuseEffect
יכולים לרוץ עד שה-Javascript הורד. זאת הסיבה ש-React מזהיר כשקומפוננטה שהיא server-rendered מכילהuseLayoutEffect
. על מנת לתקן זאת, או שתעביר את הלוגיקה ל-useEffect
(אם זה לא נחוץ לרינדור הראשון), או המתן עם הצגת הקומפוננטה עד לאחר רינדור הקליינט (אם ה-HTML נראה שבור עד ש-useLayoutEffect
רץ).על מנת להדיר קומפוננטה שצריכה layout effects מ-server-rendered HTML, רנדר אותה בתנאי עם
showChild && <Child />
ועכב את הצגתה עםuseEffect(() => { setShowChild(true); }, [])
. בדרך זו, ממשק המשתמש לא מופיע שבור לפני הידרציה.
useDebugValue
useDebugValue(value)
ניתן להשתמש ב-useDebugValue
על מנת להציג label ל-hooks מותאמים אישית ב-React DevTools.
לדוגמה, שקול את ה-hook useFriendStatus
שמתואר ב”בניית Hooks משלך”:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// הראה label ב-DevTools ליד ה-Hook הזה // לדוגמה "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
טיפ
אנו לא ממליצים להוסיף ערכי debug לכל Hook מותאם אישית. זה נחוץ במיוחד ל-Hooks מותאמים אישית שחלק מספריות משותפות.
דחה formatting של ערכי debug
במקרים מסוימים לבצע formatting לערך יכול להיות פעולה יקרה. זה גם לא נחוץ אלא אם ה-Hook נבדק.
מסיבה זו useDebugValue
מקבל פונקציית formatting כפרמטר שני אופציונלי. קוראים לפונקציה זו רק אם ה-Hooks נבדקים. היא מקבלת את ערך ה-debug כפרמטר וצריכה להחזיר ערך הצגה שעבר formatting.
לדוגמה Hook מותאם אישית שמחזיר ערך Date
יוכל להימנע מלקרוא לפונקציית toDateString
באופן לא נחוץ על ידי העברת ה-formatter הבא:
useDebugValue(date, date => date.toDateString());
useDeferredValue
const deferredValue = useDeferredValue(value);
useDeferredValue
accepts a value and returns a new copy of the value that will defer to more urgent updates. If the current render is the result of an urgent update, like user input, React will return the previous value and then render the new value after the urgent render has completed.
This hook is similar to user-space hooks which use debouncing or throttling to defer updates. The benefits to using useDeferredValue
is that React will work on the update as soon as other work finishes (instead of waiting for an arbitrary amount of time), and like startTransition
, deferred values can suspend without triggering an unexpected fallback for existing content.
Memoizing deferred children
useDeferredValue
only defers the value that you pass to it. If you want to prevent a child component from re-rendering during an urgent update, you must also memoize that component with React.memo
or React.useMemo
:
function Typeahead() {
const query = useSearchQuery('');
const deferredQuery = useDeferredValue(query);
// Memoizing tells React to only re-render when deferredQuery changes,
// not when query changes.
const suggestions = useMemo(() =>
<SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">
{suggestions}
</Suspense>
</>
);
}
Memoizing the children tells React that it only needs to re-render them when deferredQuery
changes and not when query
changes. This caveat is not unique to useDeferredValue
, and it’s the same pattern you would use with similar hooks that use debouncing or throttling.
useTransition
const [isPending, startTransition] = useTransition();
Returns a stateful value for the pending state of the transition, and a function to start it.
startTransition
lets you mark updates in the provided callback as transitions:
startTransition(() => {
setCount(count + 1);
})
isPending
indicates when a transition is active to show a pending state:
function App() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
function handleClick() {
startTransition(() => {
setCount(c => c + 1);
})
}
return (
<div>
{isPending && <Spinner />}
<button onClick={handleClick}>{count}</button>
</div>
);
}
Note:
Updates in a transition yield to more urgent updates such as clicks.
Updates in a transitions will not show a fallback for re-suspended content. This allows the user to continue interacting with the current content while rendering the update.
useId
const id = useId();
useId
is a hook for generating unique IDs that are stable across the server and client, while avoiding hydration mismatches.
Note
useId
is not for generating keys in a list. Keys should be generated from your data.
For a basic example, pass the id
directly to the elements that need it:
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input id={id} type="checkbox" name="react"/>
</>
);
};
For multiple IDs in the same component, append a suffix using the same id
:
function NameFields() {
const id = useId();
return (
<div>
<label htmlFor={id + '-firstName'}>First Name</label>
<div>
<input id={id + '-firstName'} type="text" />
</div>
<label htmlFor={id + '-lastName'}>Last Name</label>
<div>
<input id={id + '-lastName'} type="text" />
</div>
</div>
);
}
Note:
useId
generates a string that includes the:
token. This helps ensure that the token is unique, but is not supported in CSS selectors or APIs likequerySelectorAll
.
useId
supports anidentifierPrefix
to prevent collisions in multi-root apps. To configure, see the options forhydrateRoot
andReactDOMServer
.
Library Hooks
The following Hooks are provided for library authors to integrate libraries deeply into the React model, and are not typically used in application code.
useSyncExternalStore
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
useSyncExternalStore
is a hook recommended for reading and subscribing from external data sources in a way that’s compatible with concurrent rendering features like selective hydration and time slicing.
This method returns the value of the store and accepts three arguments:
subscribe
: function to register a callback that is called whenever the store changes.getSnapshot
: function that returns the current value of the store.getServerSnapshot
: function that returns the snapshot used during server rendering.
The most basic example simply subscribes to the entire store:
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
However, you can also subscribe to a specific field:
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
);
When server rendering, you must serialize the store value used on the server, and provide it to useSyncExternalStore
. React will use this snapshot during hydration to prevent server mismatches:
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
() => INITIAL_SERVER_SNAPSHOT.selectedField,
);
Note:
getSnapshot
must return a cached value. If getSnapshot is called multiple times in a row, it must return the same exact value unless there was a store update in between.A shim is provided for supporting multiple React versions published as
use-sync-external-store/shim
. This shim will preferuseSyncExternalStore
when available, and fallback to a user-space implementation when it’s not.As a convenience, we also provide a version of the API with automatic support for memoizing the result of getSnapshot published as
use-sync-external-store/with-selector
.
useInsertionEffect
useInsertionEffect(didUpdate);
The signature is identical to useEffect
, but it fires synchronously before all DOM mutations. Use this to inject styles into the DOM before reading layout in useLayoutEffect
. Since this hook is limited in scope, this hook does not have access to refs and cannot schedule updates.
Note:
useInsertionEffect
should be limited to css-in-js library authors. PreferuseEffect
oruseLayoutEffect
instead.