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
相关产品推荐
相关产品推荐

