diff --git a/src/content/docs/zh-cn/guides/actions.mdx b/src/content/docs/zh-cn/guides/actions.mdx index daf8f8b96c1a0..6c5cac90040d5 100644 --- a/src/content/docs/zh-cn/guides/actions.mdx +++ b/src/content/docs/zh-cn/guides/actions.mdx @@ -24,7 +24,7 @@ Actions 是在 `src/actions/index.ts` 中导出的 `server` 对象中定义的 ```ts title="src/actions/index.ts" import { defineAction } from 'astro:actions'; -import { z } from 'astro:schema'; +import { z } from 'astro/zod'; export const server = { myAction: defineAction({ /* ... */ }) @@ -62,22 +62,22 @@ async () => { } ``` -2. 导入 `astro:actions` 中的 `defineAction()` 工具以及 `astro:schema` 中的 `z` 对象。 +2. 导入 `astro:actions` 中的 `defineAction()` 工具以及 `astro/zod` 中的 `z` 对象。 ```ts ins={1-2} title="src/actions/index.ts" import { defineAction } from 'astro:actions'; - import { z } from 'astro:schema'; + import { z } from 'astro/zod'; export const server = { // action 声明 } ``` -3. 使用 `defineAction()` 工具定义一个 `getGreeting` action。`input` 属性将使用 [Zod](https://zod.dev) scheme 验证输入参数,`handler()` 函数包含要在服务器上运行的后端逻辑。 +3. 使用 `defineAction()` 工具定义一个 `getGreeting` action。`input` 属性将使用 [Zod schema](/zh-cn/reference/modules/astro-zod/#common-data-type-validators) scheme 验证输入参数,`handler()` 函数包含要在服务器上运行的后端逻辑。 ```ts ins={5-12} title="src/actions/index.ts" import { defineAction } from 'astro:actions'; - import { z } from 'astro:schema'; + import { z } from 'astro/zod'; export const server = { getGreeting: defineAction({ @@ -206,13 +206,13 @@ const updatedLikes = await actions.likePost.orThrow({ postId: 'example' }); #### 创建一个 `ActionError` -为了抛出一个错误,从 `astro:actions` 模块中导入 `ActionError()` 类。传递一个人类可读的状态 `code`(例如 `"NOT_FOUND"` 或 `"BAD_REQUEST"`),以及一个可选的 `message` 以提供有关错误的更多信息。 +为了抛出一个错误,从 `astro:actions` 模块中导入 [`ActionError()` 类](/zh-cn/reference/modules/astro-actions/#actionerror)。传递一个人类可读的状态 `code`(例如 `"NOT_FOUND"` 或 `"BAD_REQUEST"`),以及一个可选的 `message` 以提供有关错误的更多信息。 当用户并未登录,且在检查了假设的 “use-session” cookie 进行身份验证后,这个例子从 `likePost` action 抛出一个错误: ```ts title="src/actions/index.ts" ins=/ActionError(?= )/ ins={9-12} import { defineAction, ActionError } from "astro:actions"; -import { z } from "astro:schema"; +import { z } from "astro/zod"; export const server = { likePost: defineAction({ @@ -289,7 +289,7 @@ Actions 默认接受 JSON 数据。要接受来自 HTML 表单的表单数据, ```ts title="src/actions/index.ts" ins={6} import { defineAction } from 'astro:actions'; -import { z } from 'astro:schema'; +import { z } from 'astro/zod'; export const server = { comment: defineAction({ @@ -300,6 +300,55 @@ export const server = { } ``` +### 在表单输入中使用验证器 + +当你的 action [配置为接受表单数据](/zh-cn/reference/modules/astro-actions/#accept-属性) 时,你可以使用任何 Zod 验证器来验证字段(例如用于日期输入的 `z.coerce.date()`)。在 `z.object()` 验证器上也支持扩展函数,包括 `.refine()`、`.transform()` 和 `.pipe()`。 + +此外,Astro 在底层为你提供了特殊处理,以便方便地验证以下类型的字段输入: + +- 使用 `z.number()` 验证类型为 `number` 的输入 +- 使用 `z.coerce.boolean()` 验证类型为 `checkbox` 的输入 +- 使用 `z.instanceof(File)` 验证类型为 `file` 的输入 +- 使用 `z.array(/* validator */)` 验证相同 `name` 的多个输入 +- 使用 `z.string()` 验证所有其他输入 + +当你的表单提交时若包含空输入,输出类型可能与你的 `input` 校验器不匹配。空值会被转换为 `null`,但在验证数组或布尔值时除外。例如,如果一个类型为 `text` 的输入以空值提交,结果将是 `null`,而不是空字符串(`""`)。 + +要应用不同验证器的并集,请使用 `z.discriminatedUnion()` 包装器,根据特定表单字段缩小类型。此示例接受 “create” 或 “update” 用户的表单提交,使用名称为 `type` 的表单字段来确定应针对哪个对象进行验证: + +```ts title="src/actions/index.ts" {7-21} "create" "update" +import { defineAction } from 'astro:actions'; +import { z } from 'astro/zod'; + +export const server = { + changeUser: defineAction({ + accept: 'form', + input: z.discriminatedUnion('type', [ + z.object({ + // 当 `type` 字段的值为 `create` 时匹配 + type: z.literal('create'), + name: z.string(), + email: z.string().email(), + }), + z.object({ + // 当 `type` 字段的值为 `update` 时匹配 + type: z.literal('update'), + id: z.number(), + name: z.string(), + email: z.string().email(), + }), + ]), + async handler(input) { + if (input.type === 'create') { + // 输入为 { type: 'create', name: string, email: string } + } else { + // 输入为 { type: 'update', id: number, name: string, email: string } + } + }, + }), +}; +``` + ### 校验表单数据 Actions 将解析提交的表单数据为一个对象,使用每个输入的 `name` 属性的值作为对象键。例如,包含 `` 的表单将被解析为一个对象,如 `{ search: 'user input' }`。你的 action 的 `input` 模式将用于验证此对象。 @@ -328,7 +377,7 @@ Actions 将解析提交的表单数据为一个对象,使用每个输入的 `n ```ts title="src/actions/index.ts" ins={5-12} import { defineAction } from 'astro:actions'; - import { z } from 'astro:schema'; + import { z } from 'astro/zod'; export const server = { newsletter: defineAction({ @@ -442,7 +491,7 @@ import { actions } from 'astro:actions'; ```ts title="src/actions/index.ts" mark={10} import { defineAction } from 'astro:actions'; -import { z } from 'astro:schema'; +import { z } from 'astro/zod'; export const server = { createProduct: defineAction({ @@ -655,7 +704,7 @@ export const onRequest = defineMiddleware(async (context, next) => { 要授权 action 请求,请在 action handler 中添加身份验证检查。你可能希望使用 [身份验证库](/zh-cn/guides/authentication/) 来处理会话管理和用户信息。 -Action 公开完整的 APIContext 对象,以访问从中间件传递的属性,使用 `context.locals`。当用户未经授权时,你可以使用 `UNAUTHORIZED` 代码引发 `ActionError`: +Action 公开 [`APIContext` 对象的一个子集](/zh-cn/reference/modules/astro-actions/#actionapicontext),以访问从中间件传递的属性,使用 `context.locals`。当用户未经授权时,你可以使用 `UNAUTHORIZED` 代码引发 `ActionError`: ```ts title="src/actions/index.ts" {6-8} import { defineAction, ActionError } from 'astro:actions'; @@ -678,7 +727,7 @@ export const server = { Astro 建议从 action handler 中授权用户会话,以遵从每个 action 的权限级别和速率限制。但是,你也可以从中间件中拦截所有 action 请求(或一组 action 请求)。 -使用 `getActionContext()` 函数从你的中间件中检索有关任何传入 action 请求的信息。这包括 action 名称以及该 action 是否是使用客户端远程过程调用(RPC)函数(例如 `actions.blog.like()`)或 HTML 表单调用的。 +使用 [`getActionContext()` 函数](/zh-cn/reference/modules/astro-actions/#getactioncontext) 从你的中间件中检索有关任何传入 action 请求的信息。这包括 action 名称以及该 action 是否是使用客户端远程过程调用(RPC)函数(例如 `actions.blog.like()`)或 HTML 表单调用的。 以下示例拒绝所有没有有效会话令牌的 action 请求。如果检查失败,将返回一个 “Forbidden” 响应。注意:此方法确保只有在会话存在时才能访问 action,但不是安全授权的替代品。 diff --git a/src/content/docs/zh-cn/reference/modules/astro-actions.mdx b/src/content/docs/zh-cn/reference/modules/astro-actions.mdx index c14e9f065cf4d..447adfb66f1e6 100644 --- a/src/content/docs/zh-cn/reference/modules/astro-actions.mdx +++ b/src/content/docs/zh-cn/reference/modules/astro-actions.mdx @@ -19,26 +19,33 @@ Action 帮助你构建一个类型安全的后端,你可以从客户端代码 ## 从 `astro:actions` 导入 ```js -import { +import { + ACTION_QUERY_PARAMS, + ActionError, actions, defineAction, - isInputError, + getActionContext, + getActionPath, isActionError, - ActionError, + isInputError, } from 'astro:actions'; ``` ### `defineAction()`
-(\{ accept, input, handler \}) => ActionClient
-**类型:**`(input, context) => any`
+**类型:**(input: TInputSchema, context: ActionAPIContext) => TOutput | Promise\
-- 使用 `z.number()` 验证类型为 `number` 的输入
-- 使用 `z.coerce.boolean()` 验证类型为 `checkbox` 的输入
-- 使用 `z.instanceof(File)` 验证类型为 `file` 的输入
-- 使用 `z.array(/* validator */)` 验证相同 `name` 的多个输入
-- 使用 `z.string()` 验证所有其他输入
+**类型:**`"form" | "json"`
+**默认值:**`json`
+
+
+**类型:**Record\
+
-**类型:**(error?: unknown | ActionError) => boolean
-
-**类型:** (error?: unknown | ActionError) => boolean
-
-
-ActionErrorCode
-
+ +**类型:**`string` +
+ +用于传递堆栈跟踪的可选属性。 ### `getActionContext()`
-**类型:** `(context: APIContext) => ActionMiddlewareContext`
+**类型:**(context: APIContext) => AstroActionContext
-**类型:** `{ calledFrom: 'rpc' | 'form', name: string, handler: () => Promise\{ calledFrom: "rpc" | "form"; name: string; handler: () => Promise\<SafeResult\>; \} | undefined
+ +**类型:**`"rpc" | "form"` +
+ +该 action 是通过 RPC 函数调用还是通过 HTML 表单的 action 调用。 + +##### `name` + ++ +**类型:**`string` +
+ +action 的名称。用于在重定向期间跟踪 action 结果的来源。 + +##### `handler()` + +
+
+**类型:**() => Promise\<SafeResult\>
+
@@ -246,7 +313,7 @@ export const onRequest = defineMiddleware(async (context, next) => { **类型:** `(actionName: string, actionResult: SerializedActionResult) => void`
-`setActionResult()` 是一个以编程方式设置中间件中 `Astro.getActionResult()` 返回值的函数。它接受 action 名称和由 [`serializeActionResult()`](#serializeactionresult) 序列化的 action 结果。 +一个以编程方式设置中间件中 `Astro.getActionResult()` 返回值的函数。它接受 action 名称和由 [`serializeActionResult()`](#serializeactionresult) 序列化的 action 结果。从中间件调用此函数将禁用 Astro 自身的 action 结果处理。 当从 HTML 表单中调用 action 以持久化和加载结果时,这很有用。 @@ -270,10 +337,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
-**类型:** `(result: SafeResult(res: SafeResult) => SerializedActionResult
-**类型:** `(result: SerializedActionResult) => SafeResult(res: SerializedActionResult) => SafeResult
-**类型:** `(action: ActionClient(action: ActionClient) => string
+ +**类型:**`{ actionName: string, actionPayload: string }` +
+ +一个包含 Astro 在处理表单 action 提交时内部使用的查询参数名称的对象。 + +当你使用 action 提交表单时,以下查询参数会被添加到 URL 中以跟踪该 action 调用: +* `actionName` - 包含正在调用的 action 名称的查询参数 +* `actionPayload` - 包含序列化表单数据的查询参数 + +当你需要在表单提交后清理 URL 时,此常量非常有用。例如,在重定向期间,你可能希望移除与 action 相关的查询参数: + +```ts title="src/pages/api/contact.ts" "ACTION_QUERY_PARAMS" +import type { APIRoute } from "astro"; +import { ACTION_QUERY_PARAMS } from 'astro:actions' + +export const GET: APIRoute = ({ params, request }) => { + const link = request.url.searchParams; + link.delete(ACTION_QUERY_PARAMS.actionName); + link.delete(ACTION_QUERY_PARAMS.actionPayload); + + return redirect(link, 303); +}; +``` + +## `astro:actions` 类型 + +```ts +import type { + ActionAPIContext, + ActionClient, + ActionErrorCode, + ActionInputSchema, + ActionReturnType, + SafeResult, + } from 'astro:actions'; +``` + +### `ActionAPIContext` + +[Astro 上下文对象](/zh-cn/reference/api-reference/) 的子集。以下属性不可用:`callAction`、`getActionResult`、`props` 和 `redirect`。 + +### `ActionClient` + +
+
+**类型:**
+* (input?: any) => Promise\<SafeResult\>
+* `{ queryString?: string; orThrow: (input?: any) => Promise
+ +**类型:**`string` +
+ +可用于构建表单 action URL 的 action 的字符串表示。当你的表单组件在多个地方使用但提交时需要重定向到不同 URL 时,这会很有用。 + +下面的示例使用 `queryString` 构建一个 URL,该 URL 将通过自定义属性传递给表单的 `action` 属性: + +```astro title="src/pages/postal-service.astro" "queryString" +--- +import { actions } from 'astro:actions'; +import FeedbackForm from "../components/FeedbackForm.astro"; + +const feedbackUrl = new URL('/feedback', Astro.url); +feedbackUrl.search = actions.myAction.queryString; +--- +
+
+**类型:**`(input?: any) => Promise
+ +**类型:**`string` +
+ +[由 IANA 定义](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) 的一组标准 HTTP 状态代码的联合类型,使用可读的人类版本作为大写字符串并以下划线分隔(例如 `BAD_REQUEST` 或 `PAYLOAD_TOO_LARGE`)。 + +### `ActionInputSchema` + +
+
+**类型:**`ZodType`
+
+
+**类型:**Awaited\
+
+ +**类型:**`{ data: TOutput, error: undefined } | { data: undefined, error: ActionError }` +
+ +表示 action 调用的结果: +* 成功时,`data` 包含 action 的输出,`error` 是 `undefined`。 +* 失败时,`error` 包含一个 [`ActionError`](#actionerror),其中包含验证错误或运行时错误,`data` 是 `undefined`。