如何用Zod对含嵌套字段的React Hook Form做条件验证?
解决React Hook Form + Zod的条件联系方式验证问题
现有方案的核心问题
- 全局
.refine()会在其他字段验证未通过时强制执行,导致用户刚解决完当前错误,又弹出新的联系方式验证错误,体验糟糕; - 取消勾选联系方式后,对应字段的验证规则仍生效,错误信息无法自动清除。
重构后的Zod Schema(用z.when实现条件验证)
利用Zod的z.when方法,将验证逻辑与字段绑定,仅在对应联系方式被勾选时触发验证,未勾选时字段自动变为可选,从根源避免错误残留:
import { z } from "zod"; import validator from "validator"; import parsePhoneNumber from "libphonenumber-js"; const schema = z.object({ // 保留你的其他字段 contactMethods: z .array(z.enum(["email", "phone"])) .min(1, "At least one contact method is required"), email: z.when( // 判断条件:是否勾选了email (data) => data.contactMethods.includes("email"), { // 勾选时:必填+格式验证 required_error: "Enter an email", schema: z.string().trim().email("Enter a valid email"), }, { // 未勾选时:允许空值/undefined,不触发验证 schema: z.string().optional().nullable(), } ), phone: z.when( (data) => data.contactMethods.includes("phone"), { // 勾选时:必填+格式处理+转换 required_error: "Enter a phone number", schema: z.string() .trim() .transform(val => val.replaceAll(" ", "")) .refine(val => validator.isMobilePhone(val), "Enter a valid phone number") .transform(val => parsePhoneNumber(val, "GB")?.number), }, { // 未勾选时:允许空值/undefined schema: z.string().optional().nullable(), } ), });
配合React Hook Form处理字段重置
为了彻底清除未勾选字段的错误,需要在用户取消勾选时清空对应字段的值,并触发重新验证:
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; function ContactForm() { const { register, setValue, formState: { errors } } = useForm({ resolver: zodResolver(schema), defaultValues: { contactMethods: [], email: "", phone: "", // 其他字段默认值 }, }); // 在勾选框的change事件中直接处理字段重置 const handleContactMethodToggle = (method, isChecked) => { if (!isChecked) { setValue(method, "", { shouldValidate: true }); } }; return ( <form> {/* 联系方式勾选框 */} <label> <input type="checkbox" {...register("contactMethods", { value: "email" })} onChange={(e) => handleContactMethodToggle("email", e.target.checked)} /> Email </label> <label> <input type="checkbox" {...register("contactMethods", { value: "phone" })} onChange={(e) => handleContactMethodToggle("phone", e.target.checked)} /> Phone </label> {/* 联系方式输入框 */} {errors.contactMethods && <p className="error">{errors.contactMethods.message}</p>} <input type="email" {...register("email")} placeholder="Enter your email" /> {errors.email && <p className="error">{errors.email.message}</p>} <input type="tel" {...register("phone")} placeholder="Enter your phone number" /> {errors.phone && <p className="error">{errors.phone.message}</p>} {/* 其他字段和提交按钮 */} </form> ); }
方案优势
- 验证顺序合理:字段级别的条件验证会和其他字段同步验证,不会出现“解决完一个错误又冒新错误”的情况;
- 错误自动清除:取消勾选时清空字段并触发验证,此时Zod会识别该字段为可选,自动清除错误信息;
- 代码更简洁:去掉冗余的全局
.refine(),逻辑更直观,维护成本更低。
内容的提问来源于stack exchange,提问作者parasomnist
相关产品推荐
相关产品推荐

