You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何根据字段值动态调整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,提问作者Емил Цоков

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:17:45