如何根据字段值动态调整Zod Schema对象的校验规则?
问题描述
我有一个包含若干字段和开关组件的表单,开关用于显示/隐藏表单部分内容,代码如下:
<Field name="field1" value={value1} /> <Field name="field2" value={value2} /> <Switch value={showShippingAddress} /> { showShippingAddress && ( <> <Field name="field3" value={value3} /> <Field name="field4" value={value4} /> </> )}
我希望在Zod Schema中实现类似以下逻辑(示例代码仅为描述预期效果,写法不正确):
z .object({ field1: z.string(), field2: z.string(), showShippingAddress: z.boolean(), field3: showShippingAddressValue ? z.string().required() : z.string().optional(), // 仅为描述预期 field4: showShippingAddressValue ? z.string().required().regex(/^...$/) : z.string().optional(), // 仅为描述预期 })
目前我只能想到一种实现方式,但校验规则增多后会变得繁琐:
z .object({ field1: z.string(), field2: z.string(), }) .and( z.object({ showShippingAddress: z.boolean(), field3: z.string().nullable(), field4: z.string().nullable(), }) .superRefine( ({ showShippingAddress, field3, field4}, { addIssue }) => { if(showShippingAddress && !field3) { addIssue({ code: z.ZodIssueCode.custom, message: `Field3 is required.` }) } if(showShippingAddress && !field4) { addIssue({ code: z.ZodIssueCode.custom, message: `Field4 is required.` }) } if(showShippingAddress && !/^...$/.test(field4)) { addIssue({ code: z.ZodIssueCode.custom, message: `Field4 is not valid.` }) } // 更多校验规则会让这里的代码越来越臃肿 }))
请问有没有更优的实现方式?
更优实现方案
方案1:使用z.discriminatedUnion(推荐)
利用Zod的判别联合类型,根据showShippingAddress的布尔值拆分两种对象结构,每种结构的校验规则独立定义,逻辑清晰易维护:
const baseSchema = z.object({ field1: z.string(), field2: z.string(), }); // 开启收货地址时的校验规则 const shippingRequiredSchema = baseSchema.extend({ showShippingAddress: z.literal(true), field3: z.string().required(), field4: z.string().required().regex(/^你的正则规则$/), }); // 关闭收货地址时的校验规则 const shippingOptionalSchema = baseSchema.extend({ showShippingAddress: z.literal(false), field3: z.string().optional().nullable(), field4: z.string().optional().nullable(), }); // 组合成最终的表单校验schema const formSchema = z.discriminatedUnion('showShippingAddress', [shippingRequiredSchema, shippingOptionalSchema]);
优势:
- 每种场景的校验规则完全隔离,新增规则直接在对应schema中添加,不会出现逻辑混乱
- Zod自动根据
showShippingAddress的值匹配对应校验逻辑,无需手动写条件判断 - TypeScript类型推导更精准,能正确识别不同开关状态下的字段可选性
方案2:使用z.when(Zod v3.21.0+ 支持)
Zod 3.21.0及以上版本支持z.when方法,可直接在对象字段内定义条件校验规则,写法更贴近你最初的预期:
const formSchema = z.object({ field1: z.string(), field2: z.string(), showShippingAddress: z.boolean(), field3: z.string().nullable().when('showShippingAddress', { is: true, then: (schema) => schema.required('Field3 is required'), otherwise: (schema) => schema.optional(), }), field4: z.string().nullable().when('showShippingAddress', { is: true, then: (schema) => schema.required('Field4 is required').regex(/^你的正则规则$/, 'Field4 is not valid'), otherwise: (schema) => schema.optional(), }), });
优势:
- 代码紧凑,所有规则在同一个对象内定义,结构直观
- 逻辑与你最初设想的一致,无需拆分多个schema
- 适合简单的条件校验场景,学习成本低
方案3:简化superRefine写法(兼容旧版本Zod)
如果你的Zod版本较低无法使用z.when,可以把重复的校验逻辑封装成函数,优化superRefine的代码结构:
// 封装必填校验函数 const validateRequiredIfShipping = (fieldValue: string | null, fieldName: string, addIssue: (issue: z.ZodIssue) => void) => { if (!fieldValue) { addIssue({ code: z.ZodIssueCode.custom, message: `${fieldName} is required.`, path: [fieldName], }); } }; // 封装正则校验函数 const validateRegexIfShipping = (fieldValue: string | null, regex: RegExp, fieldName: string, addIssue: (issue: z.ZodIssue) => void) => { if (fieldValue && !regex.test(fieldValue)) { addIssue({ code: z.ZodIssueCode.custom, message: `${fieldName} is not valid.`, path: [fieldName], }); } }; const formSchema = z.object({ field1: z.string(), field2: z.string(), showShippingAddress: z.boolean(), field3: z.string().nullable(), field4: z.string().nullable(), }).superRefine(({ showShippingAddress, field3, field4 }, { addIssue }) => { if (showShippingAddress) { validateRequiredIfShipping(field3, 'field3', addIssue); validateRequiredIfShipping(field4, 'field4', addIssue); validateRegexIfShipping(field4, /^你的正则规则$/, 'field4', addIssue); } });
优势:
- 抽离重复逻辑,代码更整洁
- 新增校验规则时只需添加对应函数调用,无需重复编写判断模板
内容的提问来源于stack exchange,提问作者Емил Цоков
相关产品推荐
相关产品推荐

