React.cache:让你的服务器组件告别“重复劳动”

一句话总结:它是专为 React 服务器组件(RSC)设计的"记忆化"利器,能让你在同一个请求中,多次调用同一个函数,却只执行一次真实逻辑。

想象一下这个场景:你点了三份同样的外卖(同一篇博客文章的数据),商家会让三个厨师各做一遍,还是让一个厨师做一份然后分装三份?当然是后者!React.cache 就是那个帮你"只做一份"的调度员。


一、🤔 为什么需要 React.cache?从"外卖比喻"说起

在 Next.js 的 App Router 中,一个页面通常由多个组件组成:布局、页面本身、generateMetadata......每个组件都可能需要同样的数据。

没有缓存时

tsx 复制代码
// 这三个地方都要用到文章数据,每次都重新查数据库!😱
// app/article/layout.tsx
const { title } = await getArticle()  // 查第一次

// app/article/page.tsx
const { title } = await getArticle()  // 查第二次

// app/article/layout.tsx 的 generateMetadata
const { title } = await getArticle()  // 查第三次

结果是:三次数据库查询,三次网络请求,三次等待时间。 这在复杂页面里就是性能灾难。

有了 React.cache 之后

tsx 复制代码
// utils.ts
import { cache } from 'react'

export const getArticle = cache(async (id) => {
  return await db.article.findUnique({ where: { id } })
})

无论你在多少个组件里调用 getArticle(id),只要 id 相同,真正的数据库查询只执行一次,后续调用直接返回缓存结果。

二、📦 核心 API:超级简单

React.cache 的 API 极其简洁,长这样:

tsx 复制代码
import { cache } from 'react'

const cachedFn = cache(originalFn)
  • 入参:一个函数(可以是同步或异步的)
  • 返回值:一个具有相同签名的"记忆化"版本
  • 缓存依据 :函数调用的所有参数 (严格相等比较 Object.is

看个实际例子:

tsx 复制代码
import { cache } from 'react'

const fetchWeather = async (city) => {
  console.log(`🌤️ 真实请求: ${city}`)
  const res = await fetch(`https://api.weather.com/${city}`)
  return res.json()
}

const getCachedWeather = cache(fetchWeather)

// 第一次调用:执行真实请求
const nyc1 = await getCachedWeather('New York')  // 日志输出

// 第二次调用(相同参数):直接返回缓存,不执行函数
const nyc2 = await getCachedWeather('New York')  // 无日志

// 不同参数:重新执行
const london = await getCachedWeather('London')   // 日志输出

注意 :缓存不仅存成功结果,也缓存错误。如果第一次调用抛异常,后续相同参数的调用也会抛出同样的异常。

三、🎯 实战场景一:共享数据快照

这是 RSC 中最经典的使用场景:多个组件需要同一份数据

在掘金的一篇实战文章中,作者展示了这样一个结构:

tsx 复制代码
// app/article/utils.ts
import { cache } from 'react'
import { db } from '@/lib/db'

export const getArticle = cache(async (id: string) => {
  // 模拟耗时的数据库查询
  await new Promise(r => setTimeout(r, 2000))
  return await db.article.findUnique({ where: { id } })
})

然后在布局和页面中同时使用:

tsx 复制代码
// app/article/[id]/layout.tsx
import { getArticle } from './utils'

export default async function Layout({ params: { id } }) {
  const { title } = await getArticle(id)  // ← 调用1
  return (
    <div className="banner">
      您正在阅读:{title}
      {children}
    </div>
  )
}

// app/article/[id]/page.tsx
import { getArticle } from './utils'

export default async function Page({ params: { id } }) {
  const { title } = await getArticle(id)  // ← 调用2,走缓存
  return <h1>{title}</h1>
}

效果 :两个组件虽然都调用了 getArticle,但数据库只查询了一次。而且因为用的是同一个记忆化函数,它们拿到的数据快照完全一致------不会出现布局显示"标题A",页面显示"标题B"的乌龙。

四、⚡ 实战场景二:预加载数据(Preload Pattern)

这是官方文档特别推荐的一个高阶技巧:在组件真正需要数据之前,提前发起请求,利用缓存把数据"预热"

tsx 复制代码
import { cache } from 'react'
import { db } from '@/lib/db'

const getUser = cache(async (id: string) => {
  return await db.user.findUnique({ where: { id } })
})

// 预加载函数:只调用,不 await
function preloadUser(id: string) {
  void getUser(id)  // 启动数据获取,但不等待
}

// 实际使用的组件
async function UserProfile({ id }: { id: string }) {
  const user = await getUser(id)  // 如果预加载已完成,这里几乎瞬间返回
  return <div>{user.name}</div>
}

// 页面组件
export default async function Page({ params }: { params: { id: string } }) {
  preloadUser(params.id)  // ← 立即开始获取用户数据
  // ... 其他计算工作 ...
  return <UserProfile id={params.id} />  // ← 用到时可能已经在缓存里了
}

这个模式的好处是并行化:在等待预加载数据的同时,React 可以继续执行其他计算或渲染其他组件,而不是串行等待。

五、⚠️ 三个你必须知道的陷阱

陷阱1:React.cache 只适用于服务器组件

这是官方文档明确强调的:cache 仅供与 React 服务器组件一起使用 。在客户端组件('use client')里用,不会报错,但缓存不生效

为什么?因为缓存的访问是通过 React 内部的**请求上下文(request storage)**实现的,而这个上下文只在服务器端渲染时才存在。

社区里已经有人在 Next.js Discord 里问过这个问题 :想把 React.cache 用在客户端组件里做请求去重,结果是------不管用。建议用 React Query、SWR 等专门的客户端缓存方案。

陷阱2:不同记忆化函数,不共享缓存

这是最容易踩的坑!看这段错误代码:

tsx 复制代码
// ❌ 错误示例
function ComponentA() {
  const getData = cache(fetchData)  // 每次渲染创建新的缓存函数
  const data = getData(id)
}

function ComponentB() {
  const getData = cache(fetchData)  // 又一个独立缓存函数
  const data = getData(id)
}

两个组件各创自己的记忆化函数,缓存互相隔离,重复劳动依然发生。

正确做法 :把 cache 调用放在模块顶层,导出一个共享的缓存函数:

tsx 复制代码
// ✅ 正确:导出同一个缓存函数
// utils/data.ts
import { cache } from 'react'

export const getData = cache(fetchData)

// 组件A和B都 import 同一个 getData

陷阱3:在组件外部调用,不触发缓存

tsx 复制代码
import { cache } from 'react'

const getUser = cache(async (id) => {
  return await db.user.findUnique({ where: { id } })
})

// ❌ 在组件外部调用,不会写入缓存
await getUser('123')

export default async function Page() {
  // ✅ 在组件内部调用,会使用缓存
  const user = await getUser('123')
}

React 只在组件渲染期间提供缓存上下文,外部调用虽然能执行函数,但缓存不会被读取或写入。

六、📊 cache vs useMemo vs memo:一张图看懂

很多初学者会混淆这几种"记忆化"机制,官方文档给了很清晰的区分:

API 适用场景 缓存范围 缓存依据 生命周期
React.cache 服务器组件 跨组件共享 函数参数 单次请求
useMemo 客户端组件 单个组件实例 依赖数组 组件生命周期
React.memo 客户端组件 组件渲染结果 Props 浅比较 组件生命周期

简单理解:

  • React.cache"一个请求内,所有人共用一份"(餐厅版:一个厨师做一份菜,分给三桌客人)
  • useMemo"一个组件内,重复渲染不重算"(餐厅版:同一桌客人反复点同一道菜,后厨不重做)
  • React.memo"Props 没变就不重渲染"(餐厅版:客人没换菜,服务员就不重新下单)

七、🔗 与 Next.js 的深度集成

在 Next.js 中,React.cache官方推荐的数据库查询缓存方案。而且 Next.js 还在持续优化它们的配合:

  • 自动去重 :最新版本的 Next.js 修复了在 "use cache" 函数中使用 React.cache 时的去重问题
  • 静态渲染 + 缓存 :当页面使用 export const revalidate = 3600 时,React.cache 会在每次重新渲染时清空缓存,配合 revalidate 实现定时更新

GitHub Trending 上的热门 UI 项目也大量使用 React.cache 来缓存组件文档和代码高亮的结果,提升首屏加载速度达 75%

八、🎯 什么时候用 React.cache?

✅ 推荐场景 ❌ 不推荐场景
在 RSC 中多次调用同一个数据获取函数 在客户端组件中
布局 + 页面 + 元数据都需要同一份数据 数据只在单个组件中使用一次
预加载数据,提升性能 需要跨请求持久化缓存(用 Redis/CDN)
数据库查询、API 调用、复杂计算 数据在客户端频繁变化

九、💡 总结:一张"外卖调度单"

回到开头的比喻。React.cache 就像餐厅里的智能调度系统

  • 它记住了"谁点了什么菜"
  • 发现重复订单时,直接复用已有的备菜
  • 所有菜品在同一批次送达,保证口味一致

核心记忆点

  1. 适用场景:仅限 React 服务器组件(RSC)
  2. 缓存依据:函数的所有参数(严格相等)
  3. 共享规则 :必须使用同一个记忆化函数实例
  4. 生命周期:每次请求独立,请求结束缓存清空
相关推荐
helloweilei8 小时前
next/dynamic和React.lazy的区别
前端·next.js
helloweilei4 天前
Next.js 中 SSR 与 RSC 的区别:别再傻傻分不清了!
next.js
Zacks_xdc5 天前
【全栈】Next.js + PostgreSQL + Vercel 实现完整登录系统(完整源码)
postgresql·全栈·next.js·登录鉴权·vercel
点正7 天前
详解TypeScript项目引用(Project References)中rootDir的坑:composite:true下为何不能指定rootDir
前端·next.js
小霖家的混江龙9 天前
仿淘宝 AI 推荐:用 Next.js 构建入门智能水果推荐 Demo
前端·人工智能·next.js
用户8815869109111 天前
2026 年,你还不懂 Nex...
next.js
城南陌上13 天前
Next.js 博客终极 SEO 优化指南:从 Sitemap 到动态 OG 图片(end)
next.js
城南陌上13 天前
提升博客体验:给 Next.js 站点添加 RSS 订阅与毫秒级全局搜索(7)
next.js
hxy060114 天前
Nextjs实现Cookie、Localstorage、SessionStorage通用的方法
next.js