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);
// ...
}
當第一次使用 data
呼叫 getMetrics
時,getMetrics
會呼叫 calculateMetrics(data)
並將結果儲存在快取中。如果再次使用相同的 data
呼叫 getMetrics
,它會傳回快取的結果,而不是再次呼叫 calculateMetrics(data)
。
參數
fn
:您想要快取其結果的函式。fn
可以接受任何參數並傳回任何值。
傳回值
cache
會傳回具有相同類型簽章的 fn
快取版本。它在此過程中不會呼叫 fn
。
使用給定參數呼叫 cachedFn
時,它會先檢查快取中是否存在快取的結果。如果存在快取的結果,它會傳回結果。如果沒有,它會使用參數呼叫 fn
,將結果儲存在快取中,然後傳回結果。只有在快取未命中的情況下才會呼叫 fn
。
注意事項
- React 會在每次伺服器請求時,將所有記憶化函式的快取失效。
- 每次呼叫 `cache` 都會建立一個新的函式。這表示多次使用相同的函式呼叫 `cache` 將會回傳不同的記憶化函式,它們並不共享相同的快取。
- `cachedFn` 也會快取錯誤。如果 `fn` 針對某些參數拋出錯誤,該錯誤將會被快取,並且當使用相同的參數呼叫 `cachedFn` 時,會重新拋出相同的錯誤。
- `cache` 僅供在 伺服器元件(Server Components) 中使用。
用法
快取耗時的計算 ...
使用 `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` 中都被渲染,這兩個元件可以共享工作,並且只針對該 `user` 呼叫 `calculateUserMetrics` 一次。
假設 `Profile` 先被渲染。它會呼叫 `getUserMetrics`,並檢查是否有快取的結果。由於這是第一次使用該 `user` 呼叫 `getUserMetrics`,因此會發生快取未命中。 `getUserMetrics` 接著會使用該 `user` 呼叫 `calculateUserMetrics`,並將結果寫入快取。
當 `TeamReport` 渲染其 `users` 列表並到達相同的 `user` 物件時,它會呼叫 `getUserMetrics` 並從快取中讀取結果。
共享資料快照 ...
若要在組件之間共享資料快照,請使用像是 fetch
的資料提取函式呼叫 cache
。當多個組件進行相同的資料提取時,只會發出一個請求,且返回的資料會被快取並在組件之間共享。所有組件在伺服器渲染過程中都參考相同的資料快照。
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
都渲染相同的 城市,它們將會從 記憶函式 收到相同的資料快照。
如果 AnimatedWeatherCard
和 MinimalWeatherCard
提供不同的 城市 參數給 getTemperature
,那麼 fetchTemperature
將會被呼叫兩次,且每個呼叫位置都會收到不同的資料。
城市 作為快取鍵值。
預載資料
透過快取長時間運行的資料提取,您可以在渲染組件之前啟動非同步工作。
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
,但請注意,它並未使用返回的資料。這個及早的 getUser
呼叫會在 Page
進行其他計算工作和渲染子組件時啟動非同步資料庫查詢。
渲染 Profile
時,我們再次呼叫 getUser
。如果初始的 getUser
呼叫已經返回並快取了使用者資料,那麼當 Profile
請求並等待此資料 時,它可以簡單地從快取中讀取,而無需再次進行遠程程序呼叫。如果 初始資料請求 還未完成,以此模式預載資料可以減少資料提取的延遲。
深入探討
當評估一個非同步函式時,您將會收到該工作的Promise。Promise 持有該工作的狀態(*pending*、*fulfilled*、*failed*)及其最終的結果。
在此範例中,非同步函式 fetchData
返回一個正在等待 fetch
的 promise。
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}
第一次呼叫 getData
時,從 fetchData
返回的 promise 會被快取。後續的查詢將會返回相同的 promise。
請注意,第一次 getData
呼叫沒有 await
,而第二次有。await
是一個 JavaScript 運算子,它會等待並返回 promise 的結果。第一次 getData
呼叫只是啟動 fetch
來快取 promise,以便第二次 getData
查詢。
如果在 第二次呼叫 時 promise 仍處於 *pending* 狀態,則 await
將會暫停以等待結果。最佳化的部分是,當我們等待 fetch
時,React 可以繼續進行計算工作,從而減少 第二次呼叫 的等待時間。
如果 Promise 已經完成,無論是錯誤還是 *已實現* 的結果,await
都會立即返回該值。在這兩種情況下,效能都會有所提升。
深入探討
所有提到的 API 都提供記憶化功能,但它們的區別在於它們的記憶化對象、誰可以存取快取,以及它們的快取何時失效。
useMemo
一般來說,你應該使用 useMemo
來快取在客戶端元件中跨渲染的昂貴計算。例如,記憶體件內部的資料轉換。
'use client';
function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record), record);
// ...
}
function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}
在此範例中,App
使用相同的記錄渲染兩個 WeatherReport
。即使兩個元件執行相同的工作,它們也無法共享工作成果。useMemo
的快取僅限於元件的區域範圍。
但是,useMemo
可以確保如果 App
重新渲染且 record
物件沒有更改,則每個元件實例都會跳過工作並使用 avgTemp
的 memoized 值。useMemo
只會使用給定的依賴項快取 avgTemp
的最後一次計算結果。
cache
一般來說,你應該在伺服器元件中使用 cache
來記憶體可以跨元件共用的工作。
const cachedFetchReport = cache(fetchReport);
function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}
function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</>
);
}
將上一個範例改寫為使用 cache
,在本例中,第二個 WeatherReport
實例 將能夠跳過重複的工作並從與 第一個 WeatherReport
相同的快取中讀取。與前一個範例的另一個不同之處是,cache 也建議用於記憶體資料提取
,這與 useMemo
不同,後者應該僅用於計算。
目前,cache
只能在伺服器元件中使用,並且快取將在伺服器請求之間失效。
memo
如果元件的 props 沒有更改,你應該使用 memo
來防止元件重新渲染。
'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
根據 props 而不是特定計算來記憶體件渲染。與 useMemo
類似,memoized 元件只會快取最後一次渲染和最後一次 prop 值。一旦 props 更改,快取就會失效,元件就會重新渲染。
疑難排解
即使我使用相同的參數呼叫我的 memoized 函式,它仍然會執行
請參閱前面提到的陷阱
如果以上皆不適用,則可能是 React 檢查快取中是否存在某項內容的方式出了問題。
如果您的參數不是基本類型(例如:物件、函式、陣列),請確保您傳遞的是相同的物件參考。
呼叫 memoized 函式時,React 會查找輸入參數,查看結果是否已快取。 React 將使用參數的淺層相等性來判斷是否存在快取命中。
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} />
</>
);
}
在這種情況下,兩個 MapMarker
看起來像是正在執行相同的工作,並使用相同的值 {x: 10, y: 10, z:10}
呼叫 calculateNorm
。 即使物件包含相同的值,它們也不是相同的物件參考,因為每個元件都會建立自己的 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
。 這是可行的,因為維度本身就是基本類型。
另一種解決方案可能是將向量物件本身作為 prop 傳遞給元件。 我們需要將相同的物件傳遞給兩個元件實例。
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} />
</>
);
}