关于 safe-action-client
safe-action-client 是用于在 TypeScript 中定义和调用 next-safe-action 服务端 action 的技能,涵盖链式客户端 API、模式验证、中间件和客户端 Hooks。
主要功能
- 01配置链式 action 客户端
- 02使用模式验证输入与输出
- 03添加验证前后中间件
- 04定义元数据和绑定参数模式
- 05使用 action 与有状态客户端 Hooks
适用场景
- 01设置类型安全的服务端 action
- 02验证 action 输入和返回值
- 03通过 Hooks 处理客户端 action 状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ next-safe-action/ skills --skill safe-action-client安装后在 Claude Code 中输入/safe-action-client确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
client-setup.mderror-handling.mdinput-output-validation.md详细介绍
next-safe-action 客户端与操作定义
快速开始
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();
// src/app/actions.ts
"use server";
import { z } from "zod";
import { actionClient } from "@/lib/safe-action";
export const greetUser = actionClient
.inputSchema(z.object({ name: z.string().min(1) }))
.action(async ({ parsedInput: { name } }) => {
return { greeting: `Hello, ${name}!` };
});
链式 API 调用顺序
createSafeActionClient(opts?)
.use(middleware) // repeatable, adds pre-validation middleware
.metadata(data) // required if defineMetadataSchema is set
.inputSchema(schema, utils?) // Standard Schema or async factory function
.bindArgsSchemas([...]) // schemas for .bind() arguments (order with inputSchema is flexible)
.useValidated(middleware) // repeatable, adds post-validation middleware (requires inputSchema or bindArgsSchemas before it)
.outputSchema(schema) // validates action return value
.action(serverCodeFn, utils?) // creates SafeActionFn
.stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useStateAction, useOptimisticStateAction, or React's useActionState)
每个方法都会返回一个新的客户端实例——调用链不可变。
入口点
| 入口点 | 环境 | 导出项 |
|---|---|---|
next-safe-action | 服务端 | createSafeActionClient、createMiddleware、createValidatedMiddleware、returnValidationErrors、returnServerError、flattenValidationErrors、formatValidationErrors、DEFAULT_SERVER_ERROR_MESSAGE、错误类、所有核心类型 |
next-safe-action/hooks | 客户端 | useAction、useOptimisticAction、useStateAction、useOptimisticStateAction、钩子类型 |
next-safe-action/stateful-hooks | 客户端 | useStateAction(为保持向后兼容而从 hooks 重新导出) |
相关文档
反模式
// BAD: Missing "use server" directive — action won't work
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
// GOOD: Always include "use server" in action files
"use server";
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
// BAD: Calling .action() without .metadata() when metadataSchema is defined
const client = createSafeActionClient({
defineMetadataSchema: () => z.object({ actionName: z.string() }),
});
client.action(async () => {}); // TypeScript error!
// GOOD: Always provide metadata before .action() when schema is defined
client
.metadata({ actionName: "myAction" })
.action(async () => {});
// BAD: Returning an error instead of throwing
export const myAction = actionClient
.inputSchema(z.object({ email: z.string().email() }))
.action(async ({ parsedInput }) => {
const exists = await db.user.findByEmail(parsedInput.email);
if (exists) {
return { error: "Email taken" }; // Not type-safe, not standardized
}
});
// GOOD: Use returnValidationErrors for field-level errors
import { returnValidationErrors } from "next-safe-action";
export const myAction = actionClient
.inputSchema(z.object({ email: z.string().email() }))
.action(async ({ parsedInput }) => {
const exists = await db.user.findByEmail(parsedInput.email);
if (exists) {
returnValidationErrors(z.object({ email: z.string().email() }), {
email: { _errors: ["Email is already in use"] },
});
}
return { success: true };
});
对于预期的非验证业务错误(“缺货”“未找到”),请改用 returnServerError(payload)——它会将 result.serverError 设置为带类型的负载,并绕过 handleServerError。请参阅服务端错误处理。
服务端代码函数参数
传递给 .action() 的函数会接收一个对象:
.action(async ({
parsedInput, // validated input (typed from inputSchema)
clientInput, // raw client input (unknown)
bindArgsParsedInputs, // validated bind args tuple
bindArgsClientInputs, // raw bind args
ctx, // context from middleware chain
metadata, // metadata set via .metadata()
}) => {
// return data
});
对于 .stateAction(),会添加第二个参数:
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {
// prevResult is the previous SafeActionResult (structuredClone'd)
return { count: (prevResult.data?.count ?? 0) + 1 };
});
评论
支持 Markdown