
本文详解如何在 zod 中基于一个字段(如 subject)的值,动态控制另一字段(如 role)的校验逻辑——当 subject === 1 时强制 role 必填且结构有效,否则完全跳过 role 校验。
本文详解如何在 zod 中基于一个字段(如 subject)的值,动态控制另一字段(如 role)的校验逻辑——当 subject === 1 时强制 role 必填且结构有效,否则完全跳过 role 校验。
在构建表单或 API 请求 Schema 时,常需实现“条件性校验”(conditional validation):某字段是否参与校验,取决于其他字段的取值。Zod 本身不支持字段级条件开关(如 z.object().optional().when(...)),但可通过 .refine() 在整个对象层面实现精准控制——它接收完整解析后的数据对象,允许你编写任意逻辑判断,并指定错误定位路径。
以下是一个典型场景:仅当 subject === 1 时,role 字段必须存在且符合 z.object({ owner: z.number(), stranger: z.number() }) 结构;若 subject !== 1,则 role 可完全缺失或为 undefined,且不应触发任何校验错误。
✅ 正确实现方式(推荐):
import { z } from 'zod';
const schema = z.object({
subject: z.number().default(0),
gender: z.object({
owner: z.number(),
stranger: z.number().array().min(1),
}),
age: z.object({
owner: z.number(),
stranger: z.number().array().min(1),
}),
// role 字段保持原始定义(非必需),由 refine 统一管控其存在性与结构
role: z.object({
owner: z.number(),
stranger: z.number(),
}).optional(), // ← 关键:显式声明 .optional(),允许缺失
}).refine(
(data) => {
// 当 subject 为 1 时,role 必须存在且满足结构
if (data.subject === 1) {
return data.role !== undefined &&
typeof data.role === 'object' &&
'owner' in data.role &&
'stranger' in data.role;
}
// subject ≠ 1 时,无需 role,直接通过
return true;
},
{
message: "Role is required and must have 'owner' and 'stranger' properties when subject equals 1",
path: ['role'], // 错误将精准标记在 role 字段下,提升用户体验
}
);? 关键要点说明:
- .optional() 不可省略:若 role 未声明为 .optional(),Zod 默认要求其必填,会导致 subject !== 1 场景下提前失败,refine 甚至无法执行;
- refine 作用于整个对象:它接收已解析的 data(类型为 { subject: number; role?: {...}; ... }),因此可自由访问任意字段并组合判断;
- path 配置增强可调试性:设置 path: ['role'] 后,校验失败时 error.issues 将明确指向 role 字段,前端可据此高亮对应输入框;
- 避免副作用校验:refine 内部不应修改 data,仅作只读判断;如需默认值填充(如 role: { owner: 0, stranger: 0 }),应在 .transform() 或业务层处理,而非 refine 中。
⚠️ 注意事项:
- refine 仅在 parse() 或 safeParse() 的最终校验阶段执行,不会影响中间解析行为;
- 若需更复杂条件(如多字段联动、嵌套路径判断),可在 refine 回调中编写完整业务逻辑,Zod 不限制表达能力;
- 不要滥用 refine 替代基础类型约束——例如 role.owner 的数值范围应仍在 z.number().min(0) 等原子校验中定义,refine 专注“存在性 + 条件触发”。
总结:Zod 的 .refine() 是实现跨字段条件验证的核心机制。通过将条件逻辑上提至对象层级、合理使用 .optional()、精准配置 path,即可优雅支撑动态表单规则,兼顾类型安全与运行时灵活性。








