cache
允许你缓存数据获取或计算的结果。
¥cache
lets you cache the result of a data fetch or computation.
const cachedFn = cache(fn);
参考
¥Reference
cache(fn)
在任何组件外部调用 cache
以创建具有缓存的函数版本。
¥Call cache
outside of any components to create a version of the function with caching.
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)
并将结果存储在缓存中。如果使用相同的 data
再次调用 getMetrics
,它将返回缓存的结果,而不是再次调用 calculateMetrics(data)
。
¥When getMetrics
is first called with data
, getMetrics
will call calculateMetrics(data)
and store the result in cache. If getMetrics
is called again with the same data
, it will return the cached result instead of calling calculateMetrics(data)
again.
参数
¥Parameters
-
fn
:你想要缓存结果的函数。fn
可以接受任何参数并返回任何值。¥
fn
: The function you want to cache results for.fn
can take any arguments and return any value.
返回
¥Returns
cache
返回具有相同类型签名的 fn
的缓存版本。在此过程中它不会调用 fn
。
¥cache
returns a cached version of fn
with the same type signature. It does not call fn
in the process.
当使用给定参数调用 cachedFn
时,它首先检查缓存中是否存在缓存结果。如果存在缓存结果,则返回结果。如果没有,则使用参数调用 fn
,将结果存储在缓存中,然后返回结果。唯一一次调用 fn
是当缓存未命中时。
¥When calling cachedFn
with given arguments, it first checks if a cached result exists in the cache. If a cached result exists, it returns the result. If not, it calls fn
with the arguments, stores the result in the cache, and returns the result. The only time fn
is called is when there is a cache miss.
注意事项
¥Caveats
-
React 将使每个服务器请求的所有记忆函数的缓存失效。
¥React will invalidate the cache for all memoized functions for each server request.
-
每次调用
cache
都会创建一个新函数。这意味着多次使用同一函数调用cache
将返回不共享相同缓存的不同记忆函数。¥Each call to
cache
creates a new function. This means that callingcache
with the same function multiple times will return different memoized functions that do not share the same cache. -
cachedFn
也会缓存错误。如果fn
对某些参数抛出错误,它将被缓存,并且当使用这些相同的参数调用cachedFn
时,会重新抛出相同的错误。¥
cachedFn
will also cache errors. Iffn
throws an error for certain arguments, it will be cached, and the same error is re-thrown whencachedFn
is called with those same arguments. -
cache
仅适用于 服务器组件。¥
cache
is for use in Server Components only.
用法
¥Usage
缓存昂贵的计算
¥Cache an expensive computation
使用 cache
跳过重复的工作。
¥Use cache
to skip duplicate work.
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);
// ...
}
// ...
}
如果在 Profile
和 TeamReport
中渲染相同的 user
对象,则两个组件可以共享工作,并且只为 user
调用 calculateUserMetrics
一次。
¥If the same user
object is rendered in both Profile
and TeamReport
, the two components can share work and only call calculateUserMetrics
once for that user
.
假设首先渲染 Profile
。它会调用getUserMetrics
,并检查是否有缓存的结果。由于这是第一次使用 user
调用 getUserMetrics
,因此会出现缓存未命中。然后,getUserMetrics
将使用 user
调用 calculateUserMetrics
,并将结果写入缓存。
¥Assume Profile
is rendered first. It will call getUserMetrics
, and check if there is a cached result. Since it is the first time getUserMetrics
is called with that user
, there will be a cache miss. getUserMetrics
will then call calculateUserMetrics
with that user
and write the result to cache.
当 TeamReport
渲染其 users
列表并到达相同的 user
对象时,它将调用 getUserMetrics
并从缓存中读取结果。
¥When TeamReport
renders its list of users
and reaches the same user
object, it will call getUserMetrics
and read the result from cache.
分享数据快照
¥Share a snapshot of data
要在组件之间共享数据快照,请使用 fetch
等数据获取函数调用 cache
。当多个组件进行相同的数据获取时,仅发出一个请求,并且返回的数据被缓存并在组件之间共享。所有组件都引用服务器渲染中的相同数据快照。
¥To share a snapshot of data between components, call cache
with a data-fetching function like fetch
. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.
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
都针对同一城市进行渲染,则它们将从记忆函数接收相同的数据快照。
¥If AnimatedWeatherCard
and MinimalWeatherCard
both render for the same city, they will receive the same snapshot of data from the memoized function.
如果 AnimatedWeatherCard
和 MinimalWeatherCard
为 getTemperature
提供不同的 city 参数,则 fetchTemperature
将被调用两次,每个调用站点将收到不同的数据。
¥If AnimatedWeatherCard
and MinimalWeatherCard
supply different city arguments to getTemperature
, then fetchTemperature
will be called twice and each call site will receive different data.
city 充当缓存键。
¥The city acts as a cache key.
预加载数据
¥Preload data
通过缓存长时间运行的数据获取,你可以在渲染组件之前启动异步工作。
¥By caching a long-running data fetch, you can kick off asynchronous work prior to rendering the component.
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
执行其他计算工作和渲染子项时发生的异步数据库查询。
¥When rendering Page
, the component calls getUser
but note that it doesn’t use the returned data. This early getUser
call kicks off the asynchronous database query that occurs while Page
is doing other computational work and rendering children.
当渲染 Profile
时,我们再次调用getUser
。如果初始getUser
调用已经返回并缓存了用户数据,那么当 Profile
请求并等待该数据时,只需从缓存中读取即可 不需要另一个远程过程调用。如果初始数据请求尚未完成,则以此模式预加载数据可以减少数据获取的延迟。
¥When rendering Profile
, we call getUser
again. If the initial getUser
call has already returned and cached the user data, when Profile
asks and waits for this data, it can simply read from the cache without requiring another remote procedure call. If the initial data request hasn’t been completed, preloading data in this pattern reduces delay in data-fetching.
深入研究
¥Caching asynchronous work
当评估 异步函数 时,你将收到该工作的 Promise 分。Promise 保存了该工作的状态(待处理、已完成、失败)及其最终确定的结果。
¥When evaluating an asynchronous function, you will receive a Promise for that work. The promise holds the state of that work (pending, fulfilled, failed) and its eventual settled result.
在此示例中,异步函数 fetchData
返回等待 fetch
的 Promise。
¥In this example, the asynchronous function fetchData
returns a promise that is awaiting the fetch
.
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}
第一次调用 getData
时,fetchData
返回的 Promise 会被缓存。随后的查找将返回相同的 promise。
¥In calling getData
the first time, the promise returned from fetchData
is cached. Subsequent look-ups will then return the same promise.
请注意,第一个 getData
调用不会执行 await
,而 第二个 则会执行。await
是一个 JavaScript 运算符,它将等待并返回 Promise 的确定结果。第一个 getData
调用只是启动 fetch
来缓存第二个 getData
查找的 promise。
¥Notice that the first getData
call does not await
whereas the second does. await
is a JavaScript operator that will wait and return the settled result of the promise. The first getData
call simply initiates the fetch
to cache the promise for the second getData
to look-up.
如果到第二次调用,promise 仍然处于待处理状态,那么 await
将暂停等待结果。优化之处在于,当我们等待 fetch
时,React 可以继续进行计算工作,从而减少了第二次调用的等待时间。
¥If by the second call the promise is still pending, then await
will pause for the result. The optimization is that while we wait on the fetch
, React can continue with computational work, thus reducing the wait time for the second call.
如果 promise 已经解决,无论是错误还是已实现的结果,await
将立即返回该值。这两种结果都具有性能优势。
¥If the promise is already settled, either to an error or the fulfilled result, await
will return that value immediately. In both outcomes, there is a performance benefit.
深入研究
¥When should I use cache
, memo
, or useMemo
?
所有提到的 API 都提供记忆功能,但区别在于它们要记忆的内容、谁可以访问缓存以及缓存何时失效。
¥All mentioned APIs offer memoization but the difference is what they’re intended to memoize, who can access the cache, and when their cache is invalidated.
useMemo
一般来说,你应该使用 useMemo
在跨渲染的客户端组件中缓存昂贵的计算。例如,记住组件内数据的转换。
¥In general, you should use useMemo
for caching a expensive computation in a Client Component across renders. As an example, to memoize a transformation of data within a component.
'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
的缓存仅位于组件本地。
¥In this example, App
renders two WeatherReport
s with the same record. Even though both components do the same work, they cannot share work. useMemo
’s cache is only local to the component.
但是,useMemo
确实确保如果 App
重新渲染并且 record
对象没有更改,则每个组件实例将跳过工作并使用 avgTemp
的记忆值。useMemo
将仅缓存具有给定依赖的 avgTemp
的最后计算。
¥However, useMemo
does ensure that if App
re-renders and the record
object doesn’t change, each component instance would skip work and use the memoized value of avgTemp
. useMemo
will only cache the last computation of avgTemp
with the given dependencies.
cache
一般来说,你应该在服务器组件中使用 cache
来记住可以跨组件共享的工作。
¥In general, you should use cache
in Server Components to memoize work that can be shared across components.
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
不同,useMemo
只能用于计算。
¥Re-writing the previous example to use cache
, in this case the second instance of WeatherReport
will be able to skip duplicate work and read from the same cache as the first WeatherReport
. Another difference from the previous example is that cache
is also recommended for memoizing data fetches, unlike useMemo
which should only be used for computations.
此时,cache
应该仅在 Server Components 中使用,并且缓存将在跨服务器请求时失效。
¥At this time, cache
should only be used in Server Components and the cache will be invalidated across server requests.
memo
如果组件的属性未更改,你应该使用 memo
来防止组件重新渲染。
¥You should use memo
to prevent a component re-rendering if its props are unchanged.
'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
没有变化,则所有属性都不会改变,MemoWeatherReport
也不会重新渲染。
¥In this example, both MemoWeatherReport
components will call calculateAvg
when first rendered. However, if App
re-renders, with no changes to record
, none of the props have changed and MemoWeatherReport
will not re-render.
与 useMemo
相比,memo
根据属性与特定计算来记忆组件渲染。与 useMemo
类似,记忆组件仅缓存最后一次渲染和最后一个属性值。一旦属性改变,缓存就会失效,组件会重新渲染。
¥Compared to useMemo
, memo
memoizes the component render based on props vs. specific computations. Similar to useMemo
, the memoized component only caches the last render with the last prop values. Once the props change, the cache invalidates and the component re-renders.
故障排除
¥Troubleshooting
即使我使用相同的参数调用了我的记忆函数,它仍然会运行
¥My memoized function still runs even though I’ve called it with the same arguments
请参阅前面提到的陷阱
¥See prior mentioned pitfalls
-
¥Calling different memoized functions will read from different caches.
-
¥Calling a memoized function outside of a component will not use the cache.
如果以上情况都不适用,则可能是 React 如何检查缓存中是否存在某些内容存在问题。
¥If none of the above apply, it may be a problem with how React checks if something exists in cache.
如果你的参数不是 primitives(例如对象、函数、数组),请确保你传递相同的对象引用。
¥If your arguments are not primitives (ex. objects, functions, arrays), ensure you’re passing the same object reference.
当调用记忆函数时,React 将查找输入参数以查看结果是否已缓存。React 将使用参数的浅相等来确定是否存在缓存命中。
¥When calling a memoized function, React will look up the input arguments to see if a result is already cached. React will use shallow equality of the arguments to determine if there is a cache hit.
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
对象。
¥In this case the two MapMarker
s look like they’re doing the same work and calling calculateNorm
with the same value of {x: 10, y: 10, z:10}
. Even though the objects contain the same values, they are not the same object reference as each component creates its own props
object.
React 将在输入上调用 Object.is
以验证是否存在缓存命中。
¥React will call Object.is
on the input to verify if there is a cache hit.
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
。这是可行的,因为维度本身就是基础类型。
¥One way to address this could be to pass the vector dimensions to calculateNorm
. This works because the dimensions themselves are primitives.
另一种解决方案可能是将矢量对象本身作为组件传递给组件。我们需要将相同的对象传递给两个组件实例。
¥Another solution may be to pass the vector object itself as a prop to the component. We’ll need to pass the same object to both component instances.
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} />
</>
);
}