safe-action-client
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenext-safe-action Client & Action Definition
next-safe-action 客户端与Action定义
Quick Start
快速开始
ts
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();ts
// 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}!` };
});ts
// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();ts
// 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}!` };
});Chainable API Order
链式API调用顺序
createSafeActionClient(opts?)
.use(middleware) // repeatable, adds middleware to chain
.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)
.outputSchema(schema) // validates action return value
.action(serverCodeFn, utils?) // creates SafeActionFn
.stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useActionState)Each method returns a new client instance — the chain is immutable.
createSafeActionClient(opts?)
.use(middleware) // 可重复调用,为调用链添加中间件
.metadata(data) // 当定义了defineMetadataSchema时为必填项
.inputSchema(schema, utils?) // 标准Schema或异步工厂函数
.bindArgsSchemas([...]) // .bind()参数的Schema(与inputSchema的调用顺序灵活)
.outputSchema(schema) // 验证action的返回值
.action(serverCodeFn, utils?) // 创建SafeActionFn
.stateAction(serverCodeFn, utils?) // 创建SafeStateActionFn(用于useActionState)每个方法都会返回一个新的客户端实例——调用链是不可变的。
Entry Points
入口点
| Entry point | Environment | Exports |
|---|---|---|
| Server | |
| Client | |
| Client | |
| 入口点 | 运行环境 | 导出内容 |
|---|---|---|
| 服务器端 | |
| 客户端 | |
| 客户端 | |
Supporting Docs
相关文档
- Client setup & configuration
- Input & output validation with Standard Schema
- Server error handling
- 客户端设置与配置
- 使用标准Schema进行输入/输出验证
- 服务器错误处理
Anti-Patterns
反模式
ts
// 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 () => {});ts
// 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 () => {});ts
// 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 };
});ts
// 错误示例:缺少"use server"指令——action将无法正常工作
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
// 正确示例:务必在action文件中添加"use server"
"use server";
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});ts
// 错误示例:当定义了metadataSchema时,调用.action()前未调用.metadata()
const client = createSafeActionClient({
defineMetadataSchema: () => z.object({ actionName: z.string() }),
});
client.action(async () => {}); // TypeScript错误!
// 正确示例:当定义了Schema时,务必在调用.action()前提供metadata
client
.metadata({ actionName: "myAction" })
.action(async () => {});ts
// 错误示例:返回错误而非抛出错误
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" }; // 类型不安全,不符合标准
}
});
// 正确示例:使用returnValidationErrors处理字段级错误
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: ["该邮箱已被使用"] },
});
}
return { success: true };
});Server Code Function Parameters
服务器代码函数参数
The function passed to receives a single object:
.action()ts
.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
});For , a second argument is added:
.stateAction()ts
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {
// prevResult is the previous SafeActionResult (structuredClone'd)
return { count: (prevResult.data?.count ?? 0) + 1 };
});传递给的函数接收一个对象参数:
.action()ts
.action(async ({
parsedInput, // 验证后的输入(类型来自inputSchema)
clientInput, // 原始客户端输入(类型为unknown)
bindArgsParsedInputs, // 验证后的bind参数元组
bindArgsClientInputs, // 原始bind参数
ctx, // 来自中间件链的上下文
metadata, // 通过.metadata()设置的元数据
}) => {
// 返回数据
});对于,会添加第二个参数:
.stateAction()ts
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {
// prevResult是上一次的SafeActionResult(已通过structuredClone处理)
return { count: (prevResult.data?.count ?? 0) + 1 };
});