safe-action-client

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

next-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 pointEnvironmentExports
next-safe-action
Server
createSafeActionClient
,
createMiddleware
,
returnValidationErrors
,
flattenValidationErrors
,
formatValidationErrors
,
DEFAULT_SERVER_ERROR_MESSAGE
, error classes, all core types
next-safe-action/hooks
Client
useAction
,
useOptimisticAction
, hook types
next-safe-action/stateful-hooks
Client
useStateAction
(deprecated — use React's
useActionState
directly)
入口点运行环境导出内容
next-safe-action
服务器端
createSafeActionClient
createMiddleware
returnValidationErrors
flattenValidationErrors
formatValidationErrors
DEFAULT_SERVER_ERROR_MESSAGE
、错误类、所有核心类型
next-safe-action/hooks
客户端
useAction
useOptimisticAction
、钩子类型
next-safe-action/stateful-hooks
客户端
useStateAction
(已废弃——直接使用React的
useActionState

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
.action()
receives a single object:
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
.stateAction()
, a second argument is added:
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 };
});