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
仅用于 服务器组件。
用法
缓存昂贵的计算
使用 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
都针对相同的 city 进行渲染,它们将从 记忆化函数 中接收相同的数据快照。
如果 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
,但请注意它没有使用返回的数据。此提前的 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 已经确定,无论是错误还是 fulfilled 结果,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
的记忆化值。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
你应该使用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
基于 props 而不是特定计算来记忆化组件渲染。与useMemo
类似,记忆化组件只缓存最后一次使用最后一个 prop 值的渲染。一旦 props 发生更改,缓存就会失效,组件就会重新渲染。
故障排除
即使我使用相同的参数调用了我的记忆化函数,它仍然运行
参见前面提到的陷阱
如果以上情况都不适用,则可能是 React 检查缓存中是否存在某些内容的方式存在问题。
如果你的参数不是基本类型(例如:对象、函数、数组),请确保你传递的是相同的对象引用。
调用记忆化函数时,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} />
</>
);
}