
Zod 不支持直接根据字段值条件化定义其他字段,但可通过 Discriminated Union(判别联合)实现:为不同 showShippingAddress 值分别定义完整 schema,并用该字段作为判别键,让 Zod 自动选择匹配分支。
zod 不支持直接根据字段值条件化定义其他字段,但可通过 discriminated union(判别联合)实现:为不同 `showshippingaddress` 值分别定义完整 schema,并用该字段作为判别键,让 zod 自动选择匹配分支。
在表单校验中,常需根据某个开关字段(如 showShippingAddress)的布尔值,动态决定后续字段是否必需、是否参与校验。Zod 本身不提供运行时条件逻辑(如 if (x) then y.required()),但其 Discriminated Union 机制正是为此类场景设计的——它允许你为同一字段的不同字面量值(literal values)定义完全独立的 schema 分支,并由 Zod 在解析时自动识别并应用对应分支。
✅ 正确做法:使用 z.discriminatedUnion
核心思路是将 showShippingAddress 字段声明为 z.literal(true) 或 z.literal(false),而非泛化的 z.boolean(),从而构建两个互斥且可明确区分的 schema 对象,再通过 z.discriminatedUnion 组合:
import { z } from 'zod';
const formSchema = z.discriminatedUnion('showShippingAddress', [
// 分支一:不显示收货地址 → field3/field4 可选(甚至可省略)
z.object({
showShippingAddress: z.literal(false),
field1: z.string().nonempty('Field1 is required'),
field2: z.string().nonempty('Field2 is required'),
field3: z.string().optional(),
field4: z.string().optional(),
}),
// 分支二:显示收货地址 → field3/field4 必填,且可追加额外校验
z.object({
showShippingAddress: z.literal(true),
field1: z.string().nonempty('Field1 is required'),
field2: z.string().nonempty('Field2 is required'),
field3: z.string().nonempty('Shipping address is required'),
field4: z.string()
.nonempty('Phone is required')
.regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number'),
}),
]);
// ✅ 有效输入示例
formSchema.parse({
showShippingAddress: false,
field1: 'John',
field2: 'Doe',
}); // ✔️ 通过
formSchema.parse({
showShippingAddress: true,
field1: 'John',
field2: 'Doe',
field3: '123 Main St',
field4: '+1234567890',
}); // ✔️ 通过
// ❌ 无效输入(缺少必填字段)
formSchema.safeParse({
showShippingAddress: true,
field1: 'John',
field2: 'Doe',
field3: '', // 空字符串触发 .nonempty()
}); // → error: "Shipping address is required"⚠️ 注意事项与最佳实践
判别字段必须是字面量(z.literal):不能用 z.boolean(),否则 Zod 无法在编译期确定分支归属,会报错 Argument of type 'string' is not assignable to parameter of type '"showShippingAddress"'。
字段名需严格一致:z.discriminatedUnion('fieldName', [...]) 中的字符串必须与各分支 object 内的 key 完全匹配(包括大小写和拼写)。
-
避免冗余重复定义:若 field1/field2 在所有分支中校验规则相同,可先提取为公共 schema,再用 .extend() 复用:
const baseFields = z.object({ field1: z.string().nonempty(), field2: z.string().nonempty(), }); const schema = z.discriminatedUnion('showShippingAddress', [ baseFields.extend({ showShippingAddress: z.literal(false), field3: z.string().optional(), field4: z.string().optional(), }), baseFields.extend({ showShippingAddress: z.literal(true), field3: z.string().nonempty(), field4: z.string().nonempty().regex(/^[0-9]+$/), }), ]); .optional() ≠ .nullable():.optional() 表示字段可不存在或为 undefined;若后端可能传 null,应显式用 .nullable() 或 .nullish()。
类型推导精准:TypeScript 会为每个分支生成精确的类型,例如 showShippingAddress: true 时 field3 类型为 string(非 string | undefined),提升开发体验。
通过 Discriminated Union,你不仅实现了动态校验逻辑,还获得了类型安全、零运行时开销、清晰错误提示等 Zod 原生优势——这是比手动 superRefine 更健壮、更可维护的方案。

















