上下文
context
对象用于在不同 Hook 之间共享数据。它在整个请求生命周期中持续存在,并且可以在每个 Hook 中访问。通过向 req.context
设置属性,你可以有效地在多个 Hook 之间共享逻辑。
何时使用 Context
Context 为解决以下难题提供了方案:
- 在 Hook 之间传递数据:当多个 Hook 需要来自第三方 API 的数据时,可以在
beforeChange
中获取并使用,然后在afterChange
钩子中再次使用,而无需重复获取。 - 防止无限循环:在触发
afterChange
钩子的同一文档上调用payload.update()
会导致无限循环,可以通过在 context 中设置无操作条件来控制流程。 - 向 Local API 传递数据:在
req.context
上设置值并传递给payload.create()
,你可以在不添加额外字段的情况下向钩子提供额外数据。 - 在钩子与中间件或自定义端点之间传递数据:钩子可以在多个集合之间设置上下文,然后在最终的
postMiddleware
中使用。
如何使用 Context
让我们看看如何在前述两种场景中使用 context 的示例:
在钩子间传递数据
要在钩子之间传递数据,你可以在请求生命周期的早期钩子中将值赋给上下文(context),然后在后续钩子的上下文中获取这些值。
例如:
import type { CollectionConfig } from 'payload'
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
beforeChange: [
async ({ context, data }) => {
// 将customerData赋值给上下文供后续使用
context.customerData = await fetchCustomerData(data.customerID)
return {
...data,
// 这里使用的一些数据
name: context.customerData.name,
}
},
],
afterChange: [
async ({ context, doc, req }) => {
// 直接使用context.customerData而无需再次获取
if (context.customerData.contacted === false) {
createTodo('Call Customer', context.customerData)
}
},
],
},
fields: [
/* ... */
],
}
防止无限循环
假设你有一个 afterChange
钩子,你想在其中执行计算(因为计算所需的文档 ID 在 afterChange
钩子中可用,但在 beforeChange
钩子中不可用)。计算完成后,你想用计算结果更新文档。
错误示例:
import type { CollectionConfig } from 'payload'
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.update({
// 危险:在 afterChange 中更新相同 slug 的 collection 会导致无限循环!
collection: 'customers',
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
})
},
],
},
fields: [
/* ... */
],
}
为了避免上述情况,我们需要告诉 afterChange
钩子在执行更新后不再运行(从而避免再次更新自身)。我们可以通过 context 来解决这个问题。
修正后的示例:
import type { CollectionConfig } from 'payload'
const MyCollection: CollectionConfig = {
slug: 'slug',
hooks: {
afterChange: [
async ({ context, doc, req }) => {
// 如果之前设置了标志则返回
if (context.triggerAfterChange === false) {
return
}
await req.payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
context: {
// 设置标志以防止再次运行
triggerAfterChange: false,
},
})
},
],
},
fields: [
/* ... */
],
}
TypeScript
context
的默认 TypeScript 接口是 { [key: string]: unknown }
。如果你希望在项目中或为他人开发插件时使用更严格的类型定义,可以通过 declare
语法来覆盖它。
这被称为"类型增强"(type augmentation),是 TypeScript 的一项功能,允许我们为现有类型添加新的类型定义。只需在任何 .ts
或 .d.ts
文件中添加如下代码:
import { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// 创建一个新接口,将你的额外字段与原始接口合并
export interface RequestContext extends OriginalRequestContext {
myObject?: string
// ...
}
}
这将会为每个 context 对象添加类型为 string 的 myObject
属性。请确保正确遵循此示例,因为如果操作不当,类型增强可能会破坏你的类型定义。