cache מאפשר לך לשמור במטמון את התוצאה של אחזור נתונים או חישוב.
const cachedFn = cache(fn);הפניה
cache(fn)
התקשר ל-cache מחוץ לרכיבים כלשהם כדי ליצור גרסה של הפונקציה עם שמירה במטמון.
import {cache} from 'react';
import calculateMetrics from 'lib/metrics';
const getMetrics = cache(calculateMetrics);
function Chart({data}) {
const report = getMetrics(data);
// ...
}כאשר getMetrics נקרא לראשונה עם data, getMetrics יקרא ל-calculateMetrics(data) ויאחסן את התוצאה במטמון. אם getMetrics נקרא שוב עם אותו data, הוא יחזיר את התוצאה המאוחסנת במטמון במקום לקרוא שוב לcalculateMetrics(data).
פרמטרים
fn: הפונקציה שעבורה ברצונך לשמור תוצאות במטמון.fnיכול לקחת כל ארגומנט ולהחזיר כל ערך.
מחזירה
cache מחזירה גרסה שמור של fn עם חתימת סוג זהה. זה לא קורא ל-fn בתהליך.
כאשר קוראים ל-cachedFn עם ארגומנטים נתונים, הוא בודק תחילה אם קיימת תוצאה במטמון במטמון. אם קיימת תוצאה במטמון, היא מחזירה את התוצאה. אם לא, הוא קורא ל-fn עם הארגומנטים, מאחסן את התוצאה במטמון ומחזיר את התוצאה. הפעם היחידה שנקראת fn היא כשיש פספוס מטמון.
אזהרות
- React יבטל את תוקף המטמון עבור כל הפונקציות memoized עבור כל בקשת שרת.
- כל קריאה ל-
cacheיוצרת פונקציה חדשה. המשמעות היא שקריאה ל-cacheעם אותה פונקציה מספר פעמים תחזיר פונקציות שונות memoized שאינן חולקות את אותו מטמון. cachedFnישמור גם שגיאות במטמון. אםfnזורק שגיאה עבור ארגומנטים מסוימים, היא תישמר במטמון, ואותה שגיאה נזרקת מחדש כאשרcachedFnנקרא עם אותם ארגומנטים.cacheמיועד ל-use ב-רכיבי שרת בלבד.
שימוש
שמור חישוב יקר
השתמש ב-cache כדי לדלג על עבודה כפולה.
import {cache} from 'react';
import calculateUserMetrics from 'lib/user';
const getUserMetrics = cache(calculateUserMetrics);
function Profile({user}) {
const metrics = getUserMetrics(user);
// ...
}
function TeamReport({users}) {
for (let user in users) {
const metrics = getUserMetrics(user);
// ...
}
// ...
}אם אותו אובייקט user מוצג גם ב-Profile וגם ב-TeamReport, שני הרכיבים יכולים לשתף עבודה ולהתקשר ל-calculateUserMetrics רק פעם אחת עבור אותו user.
נניח ש-Profile מוצג ראשון. זה יקרא לgetUserMetrics, ויבדוק אם יש תוצאה במטמון. מכיוון שזו הפעם הראשונה שgetUserMetrics נקרא עם ה-user הזה, תהיה פספוס מטמון. לאחר מכן getUserMetrics יקרא ל-calculateUserMetrics עם ה-user הזה ויכתוב את התוצאה למטמון.
כאשר TeamReport מעבד את רשימת ה-users שלו ומגיע לאותו אובייקט user, הוא יקרא לgetUserMetrics ויקרא את התוצאה מהמטמון.
שתף תמונת מצב של נתונים
כדי לשתף תמונת מצב של נתונים בין רכיבים, התקשר ל-cache עם פונקציית שליפת נתונים כמו fetch. כאשר מספר רכיבים מבצעים את אותו אחזור נתונים, רק בקשה אחת מתבצעת והנתונים המוחזרים נשמרים במטמון ומשותפים בין רכיבים. כל הרכיבים מתייחסים לאותה תמונת מצב של נתונים על פני עיבוד השרת.
import {cache} from 'react';
import {fetchTemperature} from './api.js';
const getTemperature = cache(async (city) => {
return await fetchTemperature(city);
});
async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}
async function MinimalWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}אם AnimatedWeatherCard ו-MinimalWeatherCard שניהם יעבדו עבור אותה עיר, הם יקבלו את אותה תמונת מצב של נתונים מהפונקציה memoized.
אם AnimatedWeatherCard ו-MinimalWeatherCard מספקים ארגומנטים שונים של city ל-getTemperature, אזי fetchTemperature ייקרא פעמיים וכל אתר שיחה יקבל נתונים שונים.
city פועל כמפתח מטמון.
טען מראש נתונים
על ידי שמירה במטמון של אחזור נתונים ארוך טווח, אתה יכול להתחיל בעבודה אסינכרונית לפני רינדור הרכיב.
const getUser = cache(async (id) => {
return await db.user.query(id);
}
async function Profile({id}) {
const user = await getUser(id);
return (
<section>
<img src={user.profilePic} />
<h2>{user.name}</h2>
</section>
);
}
function Page({id}) {
// ✅ Good: start fetching the user data
getUser(id);
// ... some computational work
return (
<>
<Profile id={id} />
</>
);
}בעת רינדור Page, הרכיב קורא לgetUser אך שים לב שהוא לא use הנתונים המוחזרים. קריאת getUser המוקדמת הזו מתחילה את שאילתת מסד הנתונים האסינכרונית שמתרחשת בזמן שPage עושה עבודה חישובית אחרת ומציגה ילדים.
בעת עיבוד Profile, אנו קוראים שוב getUser. אם הקריאה הראשונית getUser כבר חזרה ושומרה את נתוני user, כאשר Profile שואל ומחכה לנתונים אלו, הוא יכול פשוט לקרוא מהמטמון מבלי להידרש לקריאת הליך מרחוק נוסף. אם בקשת הנתונים הראשונית לא הושלמה, טעינת נתונים מראש בדפוס זה מפחיתה את העיכוב באחזור הנתונים.
Deep Dive
בעת הערכת פונקציה אסינכרונית, תקבלו הבטחה עבור אותה עבודה. ההבטחה מחזיקה ב-state של אותה עבודה (בהמתנה, מומש, נכשל) והתוצאה הסופית שלה.
בדוגמה זו, הפונקציה האסינכרונית fetchData מחזירה הבטחה שמחכה ל-fetch.
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}בקריאה ל-getData בפעם הראשונה, ההבטחה שהוחזרה מ-fetchData נשמרת במטמון. חיפושים הבאים יחזירו את אותה הבטחה.
שימו לב שהקריאה הראשונה ל-getData אינה כוללת await, ואילו השנייה כן. await ממתין לפתרון ההבטחה ומחזיר את הערך שלה. הקריאה הראשונה ל-getData מתחילה את fetch כדי לשמור את ההבטחה עבור הקריאה השנייה.
אם בזמן הקריאה השנייה ההבטחה עדיין בהמתנה, אז await ימתין לתוצאה. האופטימיזציה היא שבזמן ההמתנה ל-fetch, React יכול להמשיך בעבודה חישובית, וכך לקצר את זמן ההמתנה של הקריאה השנייה.
אם ההבטחה כבר יושבה (או לשגיאה או לתוצאה ממומשת), await יחזיר את הערך מיד. בשני המצבים מתקבל שיפור ביצועים.
Deep Dive
כל ה-APIs המוזכרים מציעים memoization אבל ההבדל הוא מה הם נועדו לmemoize, מי יכול לגשת למטמון, וכאשר המטמון שלהם אינו חוקי.
useMemo
באופן כללי, אתה צריך use useMemo עבור שמירה במטמון של חישוב יקר ברכיב לקוח על פני עיבודים. כדוגמה, כדי memoלבצע טרנספורמציה של נתונים בתוך רכיב.
'use client';
function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record)), record);
// ...
}
function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}בדוגמה זו, App מעבד שני WeatherReports עם אותה רשומה. למרות ששני הרכיבים עושים את אותה עבודה, הם לא יכולים לחלוק עבודה. המטמון של useMemo הוא מקומי רק לרכיב.
עם זאת, useMemo אכן מבטיח שאם App יעבד מחדש והאובייקט record לא ישתנה, כל מופע של רכיב ידלג על עבודה וuse הערך memoized של avgTemp. useMemo ישמור רק את החישוב האחרון של avgTemp עם התלות הנתונה.
cache
באופן כללי, עליך להזין use cache ברכיבי שרת כדי memoלעצב עבודה שניתן לשתף בין רכיבים.
const cachedFetchReport = cache(fetchReport);
function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}
function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}כתיבה מחדש של הדוגמה הקודמת ל-use cache, במקרה זה המופע השני של WeatherReport יוכל לדלג על עבודה כפולה ולקרוא מאותו מטמון כמו המופע הראשון של WeatherReport. הבדל נוסף מהדוגמה הקודמת הוא ש-cache מומלץ גם עבור memoizing של שליפות נתונים, בניגוד ל-useMemo שאמור להיות used רק עבור חישובים.
בשלב זה, cache צריך להיות רק used ברכיבי שרת והמטמון יבוטל בכל בקשות השרת.
memo
עליך use memo כדי למנוע עיבוד מחדש של רכיב אם ה-props שלו לא השתנה.
'use client';
function WeatherReport({record}) {
const avgTemp = calculateAvg(record);
// ...
}
const MemoWeatherReport = memo(WeatherReport);
function App() {
const record = getRecord();
return (
<>
<MemoWeatherReport record={record} />
<MemoWeatherReport record={record} />
</>
);
}בדוגמה זו, שני הרכיבים MemoWeatherReport יקראו ל-calculateAvg כאשר הם יעבדו לראשונה. עם זאת, אם App מעבד מחדש, ללא שינויים ב-record, אף אחד מה-props לא השתנה ו-MemoWeatherReport לא יעבד מחדש.
בהשוואה ל-useMemo, memo memoמגדיל את עיבוד הרכיבים בהתבסס על props לעומת חישובים ספציפיים. בדומה ל-useMemo, הרכיב memoized מאחסן רק את העיבוד האחרון עם ערכי העזר האחרונים. ברגע שהשינוי props, המטמון מבטל והרכיב מעבד מחדש.
פתרון בעיות
הפונקציה memoized שלי עדיין פועלת למרות שקראתי לה עם אותם ארגומנטים
ראה מלכודות שהוזכרו קודם לכן
- קריאה לפונקציות memoized שונות תקרא ממטמונים שונים.
- קריאה לפונקציה memoized מחוץ לרכיב לא תגרום use למטמון.
אם אף אחד מהאמור לעיל אינו רלוונטי, ייתכן שזו בעיה עם האופן שבו React בודק אם משהו קיים במטמון.
אם הארגומנטים שלך אינם פרימיטיביים (למשל אובייקטים, פונקציות, מערכים), ודא שאתה מעביר את אותה הפניה לאובייקט.
בעת קריאה לפונקציה memoized, React יחפש את ארגומנטי הקלט כדי לראות אם התוצאה כבר נמצאת במטמון. React יעשה use שוויון רדוד של הארגומנטים כדי לקבוע אם יש פגיעה במטמון.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// 🚩 Wrong: props is an object that changes every render.
const length = calculateNorm(props);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}במקרה זה שני MapMarkers נראים כאילו הם עושים את אותה עבודה וקוראים calculateNorm עם אותו ערך של {x: 10, y: 10, z:10}. למרות שהאובייקטים מכילים את אותם ערכים, הם אינם אותה הפניה לאובייקט מכיוון שכל רכיב יוצר אובייקט props משלו.
React יתקשר ל-Object.is בקלט כדי לוודא אם יש פגיעה במטמון.
import {cache} from 'react';
const calculateNorm = cache((x, y, z) => {
// ...
});
function MapMarker(props) {
// ✅ Good: Pass primitives to memoized function
const length = calculateNorm(props.x, props.y, props.z);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}דרך אחת לטפל בזה יכולה להיות להעביר את הממדים הווקטוריים ל-calculateNorm. זה עובד מכיוון שuse הממדים עצמם הם פרימיטיביים.
פתרון נוסף עשוי להיות להעביר את האובייקט הווקטור עצמו כעזר לרכיב. נצטרך להעביר את אותו אובייקט לשני מופעי הרכיבים.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// ✅ Good: Pass the same `vector` object
const length = calculateNorm(props.vector);
// ...
}
function App() {
const vector = [10, 10, 10];
return (
<>
<MapMarker vector={vector} />
<MapMarker vector={vector} />
</>
);
}