useSyncExternalStore הוא React Hook המאפשר לך להירשם לחנות חיצונית.
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)הפניה
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
התקשר ל-useSyncExternalStore ברמה העליונה של הרכיב שלך כדי לקרוא ערך ממאגר נתונים חיצוני.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}זה מחזיר את תמונת המצב של הנתונים בחנות. עליך להעביר שתי פונקציות כארגומנטים:
- הפונקציה
subscribeצריכה להירשם לחנות ולהחזיר פונקציה שמבטלת את המנוי. - הפונקציה
getSnapshotצריכה לקרוא תמונת מצב של הנתונים מהחנות.
פרמטרים
-
subscribe: פונקציה שלוקחת ארגומנטcallbackבודד ומרשימה אותו לחנות. כאשר החנות משתנה, היא צריכה להפעיל את ה-callbackשסופק. זה יעשה use הרכיב לעיבוד מחדש. הפונקציהsubscribeצריכה להחזיר פונקציה שמנקה את המנוי. -
getSnapshot: פונקציה שמחזירה תמונת מצב של הנתונים בחנות הדרושים לרכיב. בעוד שהחנות לא השתנתה, שיחות חוזרות אלgetSnapshotחייבות להחזיר את אותו הערך. אם החנות משתנה והערך המוחזר שונה (בהשוואה לפיObject.is), React מעבד מחדש את הרכיב. -
אופציונלי
getServerSnapshot: פונקציה שמחזירה את תמונת המצב הראשונית של הנתונים בחנות. זה יהיה used רק במהלך רינדור השרת ובמהלך הידרציה של תוכן שניתנו על ידי השרת בלקוח. תמונת המצב של השרת חייבת להיות זהה בין הלקוח לשרת, ובדרך כלל עוברת בסידרה ומועברת מהשרת ללקוח. אם תשמיט ארגומנט זה, רינדור הרכיב בשרת יגרום לשגיאה.
מחזירה
תמונת המצב הנוכחית של החנות שבה אתה יכול use בלוגיקת העיבוד שלך.
אזהרות
-
תמונת המצב של החנות שהוחזרה על ידי
getSnapshotחייבת להיות בלתי ניתנת לשינוי. אם למאגר הבסיסי יש נתונים שניתנים לשינוי, החזר תמונת מצב חדשה בלתי ניתנת לשינוי אם הנתונים השתנו. אחרת, החזר תמונת מצב אחרונה במטמון. -
אם תועבר פונקציית
subscribeאחרת במהלך רינדור מחדש, React יירשם מחדש לחנות באמצעות הפונקציהsubscribeשעברה לאחרונה. אתה יכול למנוע זאת על ידי הצהרתsubscribeמחוץ לרכיב. -
אם החנות עוברת מוטציה במהלך עדכון מעבר לא חוסם, React יחזור לבצע עדכון זה כחסימה. באופן ספציפי, עבור כל עדכון מעבר, React יתקשר ל-
getSnapshotפעם שנייה רגע לפני החלת שינויים ב-DOM. אם הוא מחזיר ערך שונה ממה שהוא נקרא במקור, React יפעיל מחדש את העדכון מאפס, והפעם יחיל אותו כעדכון חוסם, כדי להבטיח שכל רכיב על המסך משקף את אותה גרסה של החנות. -
לא מומלץ להשהות רינדור על סמך ערך חנות שהוחזר על ידי
useSyncExternalStore. הסיבה היא שלא ניתן לסמן מוטציות בחנות החיצונית כעדכוני מעבר לא חוסמים, כך שהן יפעילו אתSuspensefallback הקרובה ביותר, תוך החלפת תוכן שכבר מעובד על המסך בספינר טעינה, שבדרך כלל יוצר UX גרוע.
לדוגמה, לא מעודדים את הדברים הבאים:
const LazyProductDetailPage = lazy(() => import('./ProductDetailPage.js'));
function ShoppingApp() {
const selectedProductId = useSyncExternalStore(...);
// ❌ Calling `use` with a Promise dependent on `selectedProductId`
const data = use(fetchItem(selectedProductId))
// ❌ Conditionally rendering a lazy component based on `selectedProductId`
return selectedProductId != null ? <LazyProductDetailPage /> : <FeaturedProducts />;
}שימוש
הרשמה לחנות חיצונית
רוב רכיבי ה-React שלכם יקראו רק נתונים מה-props, state, ו-context. עם זאת, לפעמים לקרוא חלק מהמחסן של useContext מ-__. שמשתנה עם הזמן. זה כולל:
- ספריות ניהול state של צד שלישי שמחזיקות state מחוץ ל-React.
- דפדפנים APIs שחושפים ערך בר שינוי ואירועים כדי להירשם לשינויים שלו.
התקשר ל-useSyncExternalStore ברמה העליונה של הרכיב שלך כדי לקרוא ערך ממאגר נתונים חיצוני.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}הוא מחזיר את תמונת מצב של הנתונים בחנות. עליך להעביר שתי פונקציות כארגומנטים:
- הפונקציה
subscribeצריכה להירשם לחנות ולהחזיר פונקציה שמבטלת את המנוי. - הפונקציה
getSnapshotצריכה לקרוא תמונת מצב של הנתונים מהחנות.
React יבצע use פונקציות אלה כדי לשמור על הרכיב שלך כמנוי לחנות ולעבד אותו מחדש בשינויים.
לדוגמה, בארגז החול למטה, todosStore מיושם כחנות חיצונית המאחסנת נתונים מחוץ ל-React. רכיב TodosApp מתחבר לאותו חנות חיצונית עם useSyncExternalStore Hook.
import { useSyncExternalStore } from 'react'; import { todosStore } from './todoStore.js'; export default function TodosApp() { const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); return ( <> <button onClick={() => todosStore.addTodo()}>Add todo</button> <hr /> <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); }
הרשמה לדפדפן API
סיבה נוספת להוסיף useSyncExternalStore היא כאשר אתה רוצה להירשם לערך כלשהו שנחשף על ידי הדפדפן ומשתנה עם הזמן. לדוגמה, נניח שאתה רוצה שהרכיב שלך יציג אם חיבור הרשת פעיל. הדפדפן חושף מידע זה באמצעות מאפיין בשם navigator.onLine.
ערך זה יכול להשתנות ללא ידיעתו של React, אז כדאי לקרוא אותו עם useSyncExternalStore.
import { useSyncExternalStore } from 'react';
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}כדי ליישם את הפונקציה getSnapshot, קרא את הערך הנוכחי מהדפדפן API:
function getSnapshot() {
return navigator.onLine;
}לאחר מכן, עליך ליישם את הפונקציה subscribe. לדוגמה, כאשר navigator.onLine משתנה, הדפדפן יפעיל את האירועים online ו-offline באובייקט window. אתה צריך לרשום את הארגומנט callback לאירועים המתאימים, ולאחר מכן להחזיר פונקציה שמנקה את המינויים:
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}כעת React יודע לקרוא את הערך מה-navigator.onLine החיצוני API וכיצד להירשם לשינויים שלו. נתק את המכשיר מהרשת ושם לב שהרכיב מעבד מחדש בתגובה:
import { useSyncExternalStore } from 'react'; export default function ChatIndicator() { const isOnline = useSyncExternalStore(subscribe, getSnapshot); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } function getSnapshot() { return navigator.onLine; } function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }
חילוץ ההיגיון ל-Hook מותאם אישית
בדרך כלל לא תכתוב useSyncExternalStore ישירות ברכיבים שלך. במקום זאת, בדרך כלל תקרא לזה מ-Hook המותאם אישית שלך. זה מאפשר לך use אותה חנות חיצונית ממרכיבים שונים.
לדוגמה, useOnlineStatus Hook מותאם אישית זה עוקב אחר האם הרשת מחוברת:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
// ...
}
function subscribe(callback) {
// ...
}כעת רכיבים שונים יכולים לקרוא ל-useOnlineStatus מבלי לחזור על היישום הבסיסי:
import { useOnlineStatus } from './useOnlineStatus.js'; function StatusBar() { const isOnline = useOnlineStatus(); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } function SaveButton() { const isOnline = useOnlineStatus(); function handleSaveClick() { console.log('✅ Progress saved'); } return ( <button disabled={!isOnline} onClick={handleSaveClick}> {isOnline ? 'Save progress' : 'Reconnecting...'} </button> ); } export default function App() { return ( <> <SaveButton /> <StatusBar /> </> ); }
הוספת תמיכה בעיבוד שרת
אם האפליקציה React שלך uses עיבוד שרת, רכיבי React שלך יפעלו גם מחוץ לסביבת הדפדפן כדי ליצור את ה-HTML הראשוני. זה יוצר כמה אתגרים בעת חיבור לחנות חיצונית:
- אם אתה מתחבר לדפדפן בלבד API, זה לא יעבוד כי use הוא לא קיים בשרת.
- אם אתה מתחבר למאגר נתונים של צד שלישי, תזדקק לנתונים שלו כדי להתאים בין השרת ללקוח.
כדי לפתור בעיות אלה, העבר פונקציה getServerSnapshot כארגומנט השלישי ל-useSyncExternalStore:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
function subscribe(callback) {
// ...
}הפונקציה getServerSnapshot דומה ל-getSnapshot, אך היא פועלת רק בשני מצבים:
- הוא פועל על השרת בעת יצירת ה-HTML.
- הוא פועל על הלקוח במהלך hydration, כלומר כאשר React לוקח את השרת HTML והופך אותו לאינטראקטיבי.
זה מאפשר לך לספק את ערך תמונת המצב הראשוני שיהיה used לפני שהאפליקציה תהפוך לאינטראקטיבית. If there is no meaningful initial value for the server rendering, omit this argument to force rendering on the client.
פתרון בעיות
אני מקבל הודעת שגיאה: “התוצאה של getSnapshot צריכה להיות מאוחסנת במטמון”
שגיאה זו פירושה שהפונקציה getSnapshot שלך מחזירה אובייקט חדש בכל פעם שהוא נקרא, לדוגמה:
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
return {
todos: myStore.todos
};
}React יעבד מחדש את הרכיב אם ערך ההחזרה getSnapshot שונה מהפעם הקודמת. זו הסיבה שאם תחזיר תמיד ערך אחר, תיכנס ללולאה אינסופית ותקבל את השגיאה הזו.
האובייקט getSnapshot שלך צריך להחזיר אובייקט אחר רק אם משהו השתנה בפועל. אם החנות שלך מכילה נתונים בלתי ניתנים לשינוי, אתה יכול להחזיר את הנתונים האלה ישירות:
function getSnapshot() {
// ✅ You can return immutable data
return myStore.todos;
}אם נתוני החנות שלך ניתנים לשינוי, הפונקציה getSnapshot שלך אמורה להחזיר תמונת מצב בלתי ניתנת לשינוי שלה. זה אומר שהוא צריך ליצור אובייקטים חדשים, אבל הוא לא צריך לעשות זאת עבור כל קריאה בודדת. במקום זאת, עליו לאחסן את תמונת המצב המחושבת האחרונה ולהחזיר את אותה תמונת מצב כמו בפעם הקודמת אם הנתונים בחנות לא השתנו. האופן שבו אתה קובע אם הנתונים הניתנים לשינוי השתנו תלוי בחנות הניתנת לשינוי שלך.
הפונקציה subscribe שלי נקראת לאחר כל עיבוד מחדש
פונקציית subscribe זו מוגדרת בתוך רכיב ולכן היא שונה בכל עיבוד מחדש:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// 🚩 Always a different function, so React will resubscribe on every re-render
function subscribe() {
// ...
}
// ...
}React יירשם מחדש לחנות שלך אם תעביר פונקציית subscribe אחרת בין עיבוד מחדש. אם יש בעיות בביצועים של CAuse וברצונך להימנע מרישום מחדש, הזז את הפונקציה subscribe החוצה:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}
// ✅ Always the same function, so React won't need to resubscribe
function subscribe() {
// ...
}לחלופין, עטוף את subscribe לתוך useCallback כדי להירשם מחדש רק כאשר ארגומנט מסוים משתנה:
function ChatIndicator({ userId }) {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ✅ Same function as long as userId doesn't change
const subscribe = useCallback(() => {
// ...
}, [userId]);
// ...
}