77 lines
2.2 KiB
Markdown
77 lines
2.2 KiB
Markdown
---
|
|
title: Per-Request Deduplication with React.cache()
|
|
impact: MEDIUM
|
|
impactDescription: deduplicates within request
|
|
tags: server, cache, react-cache, deduplication
|
|
---
|
|
|
|
## 使用 React.cache() 实现单次请求内的去重
|
|
|
|
使用 `React.cache()` 实现服务端请求去重。身份验证和数据库查询受益最大。
|
|
|
|
**用法:**
|
|
|
|
```typescript
|
|
import { cache } from 'react'
|
|
|
|
export const getCurrentUser = cache(async () => {
|
|
const session = await auth()
|
|
if (!session?.user?.id) return null
|
|
return await db.user.findUnique({
|
|
where: { id: session.user.id }
|
|
})
|
|
})
|
|
```
|
|
|
|
在单次请求中,多次调用 `getCurrentUser()` 仅执行一次查询。
|
|
|
|
**避免使用内联对象作为参数:**
|
|
|
|
`React.cache()` 使用浅相等(`Object.is`)来判断缓存是否命中。内联对象每次调用都会创建新的引用,导致无法命中缓存。
|
|
|
|
**错误做法(总是缓存未命中):**
|
|
|
|
```typescript
|
|
const getUser = cache(async (params: { uid: number }) => {
|
|
return await db.user.findUnique({ where: { id: params.uid } })
|
|
})
|
|
|
|
// 每次调用都创建新对象,永远不会命中缓存
|
|
getUser({ uid: 1 })
|
|
getUser({ uid: 1 }) // 缓存未命中,再次执行查询
|
|
```
|
|
|
|
**正确做法(缓存命中):**
|
|
|
|
```typescript
|
|
const getUser = cache(async (uid: number) => {
|
|
return await db.user.findUnique({ where: { id: uid } })
|
|
})
|
|
|
|
// 原始类型参数使用值相等判断
|
|
getUser(1)
|
|
getUser(1) // 缓存命中,返回缓存结果
|
|
```
|
|
|
|
如果必须传递对象,请传递同一个引用:
|
|
|
|
```typescript
|
|
const params = { uid: 1 }
|
|
getUser(params) // 执行查询
|
|
getUser(params) // 缓存命中(同一引用)
|
|
```
|
|
|
|
**Next.js 特别说明:**
|
|
|
|
在 Next.js 中,`fetch` API 已自动扩展了请求记忆化功能。相同 URL 和选项的请求在单次请求内会自动去重,因此 `fetch` 调用无需使用 `React.cache()`。但 `React.cache()` 对其他异步任务仍然至关重要:
|
|
|
|
- 数据库查询(Prisma、Drizzle 等)
|
|
- 大量计算
|
|
- 身份验证检查
|
|
- 文件系统操作
|
|
- 任何非 `fetch` 的异步工作
|
|
|
|
使用 `React.cache()` 在组件树中对这些操作进行去重。
|
|
|
|
参考文档:[React.cache 文档](https://react.dev/reference/react/cache)
|