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

Zod跨字段验证在Formik表单中不生效?求动态验证方案

问题解答

这不是.refine()的正常行为,问题出在Formik与Zod的集成逻辑上

Formik默认采用字段级验证策略:当单个字段值变化时,它只会验证该字段对应的Schema规则,而不会触发整个Schema的.refine()跨字段验证。只有手动调用Register.parse()时,才会执行完整的Schema校验(包括.refine()规则),这就是两种场景验证结果不一致的原因。


更实用的实现方案

方案1:用Zod的.when()实现动态字段验证(推荐)

.when()可以直接根据phoneCode的值,为phoneNumber绑定不同的验证规则,这种字段级关联验证能被Formik的默认验证逻辑识别,实时触发校验。

import { z } from 'zod';
import { Formik, Form, Field } from 'formik';

// 定义带动态规则的Schema
const PhoneFormSchema = z.object({
  phoneCode: z.string().nonempty('请选择国家/地区代码'),
  phoneNumber: z.string().nonempty('请输入手机号码').when('phoneCode', {
    // 中国大陆手机号规则
    is: 'CN',
    then: z.string().regex(/^1[3-9]\d{9}$/, '手机号格式错误,需为11位有效号码'),
    // 美国手机号规则
    is: 'US',
    then: z.string().regex(/^\d{3}-\d{3}-\d{4}$/, '手机号格式错误,需为XXX-XXX-XXXX格式'),
    // 其他国家默认规则
    otherwise: z.string().regex(/^\+\d{1,3}\d{7,15}$/, '国际号码格式错误,需包含国家代码'),
  }),
});

type PhoneFormValues = z.infer<typeof PhoneFormSchema>;

// Formik表单使用示例
export const PhoneForm = () => {
  return (
    <Formik
      initialValues={{ phoneCode: 'CN', phoneNumber: '' }}
      validationSchema={PhoneFormSchema}
      onSubmit={(values) => {
        // 此处values已通过完整校验
        console.log('提交数据:', values);
      }}
    >
      {({ errors, touched }) => (
        <Form>
          <div>
            <label>国家/地区代码</label>
            <Field name="phoneCode" as="select">
              <option value="CN">+86(中国大陆)</option>
              <option value="US">+1(美国)</option>
              <option value="OTHER">其他国家/地区</option>
            </Field>
            {touched.phoneCode && errors.phoneCode && <span className="error">{errors.phoneCode}</span>}
          </div>

          <div>
            <label>手机号码</label>
            <Field name="phoneNumber" type="text" />
            {touched.phoneNumber && errors.phoneNumber && <span className="error">{errors.phoneNumber}</span>}
          </div>

          <button type="submit">提交</button>
        </Form>
      )}
    </Formik>
  );
};

方案2:自定义Formik验证函数,强制触发完整Schema校验

如果必须使用.refine()实现复杂跨字段逻辑,可以通过Formik的validate属性手动执行完整的Schema校验,将Zod的错误转换为Formik可识别的格式:

// 定义带.refine()的Schema
const PhoneFormSchema = z.object({
  phoneCode: z.string().nonempty('请选择国家代码'),
  phoneNumber: z.string().nonempty('请输入手机号'),
}).refine((data) => {
  if (data.phoneCode === 'CN') {
    return /^1[3-9]\d{9}$/.test(data.phoneNumber);
  } else if (data.phoneCode === 'US') {
    return /^\d{3}-\d{3}-\d{4}$/.test(data.phoneNumber);
  }
  return /^\+\d{1,3}\d{7,15}$/.test(data.phoneNumber);
}, {
  message: '手机号码格式不符合所选国家规则',
  path: ['phoneNumber'], // 指定错误关联到phoneNumber字段
});

// 在Formik中使用自定义validate
<Formik
  initialValues={{ phoneCode: 'CN', phoneNumber: '' }}
  validate={(values) => {
    const validationResult = PhoneFormSchema.safeParse(values);
    const errors: Record<string, string> = {};
    
    if (!validationResult.success) {
      validationResult.error.issues.forEach(issue => {
        const fieldPath = issue.path.join('.');
        errors[fieldPath] = issue.message;
      });
    }
    
    return errors;
  }}
  onSubmit={(values) => {
    console.log('提交数据:', values);
  }}
>
  {/* 表单内容同上 */}
</Formik>

关键说明

  • .when()方案更贴合Formik的验证逻辑,能在字段值变化时实时触发对应规则的校验,用户体验更好;
  • 自定义validate函数的方式适合处理复杂的跨字段校验场景,确保完整执行所有Schema规则,包括.refine()。

内容的提问来源于stack exchange,提问作者Kaho chan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:22:27