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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 00:03:20