基于React Hook Form与Zod实现Committente类型条件验证
React Hook Form + Zod 实现按Committente类型的条件校验
问题场景
需要根据tipologia字段的值(privato/azienda)对表单字段进行差异化校验:
- 当类型为
privato时,校验Nome、Cognome、Codice Fiscale等私有字段,忽略Nome Azienda、Partita IVA - 当类型为
azienda时,校验Nome Azienda、Partita IVA等企业字段,忽略Nome、Cognome、Codice Fiscale - 两类共享Indirizzo、CAP、Città、Prov、Stato、Email、Telefono字段
原写法问题
普通z.union()无法精准基于鉴别字段匹配校验规则,且无法严格限制对应类型下不该存在的字段,导致校验逻辑失效。
正确实现方案
使用Zod的discriminatedUnion(专门处理带鉴别键的联合类型),结合字段扩展与z.never()严格限制字段存在性:
import { z } from "zod"; // 定义公共字段校验规则 const commonFields = z.object({ indirizzo: z.string().min(1, { message: "Indirizzo è richiesto" }), cap: z.string().min(1, { message: "CAP è richiesto" }), citta: z.string().min(1, { message: "Città è richiesta" }), prov: z.string().min(1, { message: "Provincia è richiesta" }), stato: z.string().min(1, { message: "Stato è richiesto" }), email: z.string().email({ message: "Email non valida" }), telefono: z.string().min(1, { message: "Telefono è richiesto" }), }); // Privato类型专属校验规则 const privatoSchema = commonFields.extend({ tipologia: z.literal("privato"), nome: z.string().min(1, { message: "Il nome è richiesto" }), cognome: z.string().min(1, { message: "Il cognome è richiesto" }), codiceFiscale: z.string().min(1, { message: "Codice Fiscale è richiesto" }), // 严格禁止Azienda类型字段存在 nomeAzienda: z.never(), partitaIva: z.never(), }); // Azienda类型专属校验规则 const aziendaSchema = commonFields.extend({ tipologia: z.literal("azienda"), nomeAzienda: z.string().min(5, { message: "Nome azienda è richiesto" }), partitaIva: z.string().min(1, { message: "Partita IVA è richiesta" }), // 严格禁止Privato类型字段存在 nome: z.never(), cognome: z.never(), codiceFiscale: z.never(), }); // 组合为鉴别式联合校验规则 const mySchema = z.discriminatedUnion("tipologia", [privatoSchema, aziendaSchema]);
结合React Hook Form使用
将上述schema传入zodResolver,实现表单校验:
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; function CommittenteForm() { const { register, handleSubmit, watch, formState: { errors } } = useForm({ resolver: zodResolver(mySchema), defaultValues: { tipologia: "privato", indirizzo: "", cap: "", // 其他默认值按需设置 }, }); const currentTipologia = watch("tipologia"); const onSubmit = (data) => { // 处理提交逻辑,此时data已符合对应类型的校验规则 console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {/* 类型选择器 */} <select {...register("tipologia")}> <option value="privato">Privato</option> <option value="azienda">Azienda</option> </select> {/* 渲染对应类型的专属字段 */} {currentTipologia === "privato" && ( <> <input {...register("nome")} placeholder="Nome" /> {errors.nome && <p className="error">{errors.nome.message}</p>} <input {...register("cognome")} placeholder="Cognome" /> {errors.cognome && <p className="error">{errors.cognome.message}</p>} <input {...register("codiceFiscale")} placeholder="Codice Fiscale" /> {errors.codiceFiscale && <p className="error">{errors.codiceFiscale.message}</p>} </> )} {currentTipologia === "azienda" && ( <> <input {...register("nomeAzienda")} placeholder="Nome Azienda" /> {errors.nomeAzienda && <p className="error">{errors.nomeAzienda.message}</p>} <input {...register("partitaIva")} placeholder="Partita IVA" /> {errors.partitaIva && <p className="error">{errors.partitaIva.message}</p>} </> )} {/* 渲染公共字段 */} <input {...register("indirizzo")} placeholder="Indirizzo" /> {errors.indirizzo && <p className="error">{errors.indirizzo.message}</p>} <input {...register("cap")} placeholder="CAP" /> {errors.cap && <p className="error">{errors.cap.message}</p>} {/* 其他公共字段(Città、Prov、Stato、Email、Telefono)同理渲染 */} <button type="submit">Invia</button> </form> ); } export default CommittenteForm;
可选调整
如果允许切换类型后保留旧字段(不强制删除),可将z.never()替换为z.optional().nullable(),此时对应字段可存在但无需校验。
内容的提问来源于stack exchange,提问作者KaMZaTa
相关产品推荐
相关产品推荐

