cache

React 服务器组件

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

注意

基于输入进行返回值缓存的优化称为 记忆化。我们将从 cache 返回的函数称为一个记忆化函数。

注意

  • 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 对象在 ProfileTeamReport 中都被渲染,那么这两个组件可以共享工作,并且只为该 user 调用一次 calculateUserMetrics

假设首先渲染了 Profile。它将调用 getUserMetrics,并检查是否有缓存的结果。由于这是第一次以该 user 调用 getUserMetrics,所以缓存未命中。于是 getUserMetrics 将会使用 user 调用 calculateUserMetrics 并将结果写入缓存。

TeamReport 使用相同的 user 对象来渲染 users 列表时,它将调用 getUserMetrics 并从缓存中读取结果。

如果可以通过传递 AbortSignal 来中止 calculateUserMetrics,则可以使用 cacheSignal(),以便在 React 完成渲染后取消昂贵的计算。calculateUserMetrics 可以直接使用 cacheSignal 在内部处理取消。

陷阱

调用不同的记忆化函数将从不同的缓存中读取数据

要访问相同的缓存,组件必须调用同一个记忆化函数。

// Temperature.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

export function Temperature({cityData}) {
// 🚩 错误示例:在组件中调用 `cache` 会为每次渲染创建新的 `getWeekReport`。
const getWeekReport = cache(calculateWeekReport);
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

// 🚩 错误示例:`getWeekReport` 仅供 `Precipitation` 组件访问。
const getWeekReport = cache(calculateWeekReport);

export function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}

在上面的示例中,PrecipitationTemperature 都将调用 cache 创建新的记忆化函数,并使用自己的缓存查找。如果两个组件都使用相同的 cityData 进行渲染,它们将重复调用 calculateWeekReport,进行重复的工作。

此外,Temperature 每次组件渲染时都创建一个 新的记忆化函数,这不允许任何缓存共享。

为了最大化缓存命中率并减少工作量,这两个组件应该调用相同的记忆化函数以访问相同的缓存。因此应该在专用模块中定义记忆化函数,以在不同组件之间使用 import-ed 进行共享。

// getWeekReport.js
import {cache} from 'react';
import {calculateWeekReport} from './report';

export default cache(calculateWeekReport);
// Temperature.js
import getWeekReport from './getWeekReport';

export default function Temperature({cityData}) {
const report = getWeekReport(cityData);
// ...
}
// Precipitation.js
import getWeekReport from './getWeekReport';

export default function Precipitation({cityData}) {
const report = getWeekReport(cityData);
// ...
}

在这里,两个组件都调用从 ./getWeekReport.js 导出的 相同的记忆化函数 来读取和写入相同的缓存。

共享数据快照

要在组件之间共享数据快照,请使用类似 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);
// ...
}

如果 AnimatedWeatherCardMinimalWeatherCard 都为相同的 city 进行渲染,它们将从 记忆化函数 接收相同的数据快照。

如果 AnimatedWeatherCardMinimalWeatherCardgetTemperature 提供不同的 city 参数,那么将调用两次 fetchTemperature,并且每个调用站点将接收不同的数据。

city 在其中充当缓存键。

注意

异步渲染 只在服务器组件中支持。

async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}

To render components that use asynchronous data in Client Components, see use() documentation.

预加载数据

通过缓存长时间运行的数据获取,你可以在渲染组件之前开始异步工作。

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}) {
// ✅ 正确示例:开始获取用户数据。
getUser(id);
// ……一些计算工作
return (
<>
<Profile id={id} />
</>
);
}

在渲染 Page 时,组件调用 getUser,但请注意它并不使用返回的数据。这个早期的