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

React表单结合Zod判别式联合实现动态字段错误校验的问题

解决React Hook Form + Zod Discriminated Union动态字段的TypeScript错误问题

问题核心

当用Zod的discriminatedUnion结合React Hook Form做动态表单时,公共字段的错误提示正常,但FCL/LCL/BULK模式下的动态字段(比如f_quantity、f_weight)访问errors对象对应属性时会触发TypeScript类型错误——因为TS无法根据当前选中的mode自动收窄errors的类型,默认认为这些动态字段可能不存在。

解决方案

1. 定义各模式的错误类型

从Zod Schema生成对应模式的错误类型,为后续类型收窄做准备:

import { z } from "zod";
import { useForm, ZodResolver } from "@hookform/resolvers/zod";

// 公共字段Schema
const commonSchema = z.object({
  from: z.string().min(1, "From is required"),
  to: z.string().min(1, "To is required"),
  modes: z.enum(["FCL", "LCL", "BULK"]),
});

// 各模式专属Schema
const fclSchema = commonSchema.extend({
  modes: z.literal("FCL"),
  f_quantity: z.number().min(1, "Quantity is required"),
  f_weight: z.number().min(0.1, "Weight is required"),
});

const lclSchema = commonSchema.extend({
  modes: z.literal("LCL"),
  l_volume: z.number().min(0.1, "Volume is required"),
});

const bulkSchema = commonSchema.extend({
  modes: z.literal("BULK"),
  b_totalWeight: z.number().min(1, "Total weight is required"),
});

// 联合Schema
const formSchema = z.discriminatedUnion("modes", [fclSchema, lclSchema, bulkSchema]);

// 生成表单数据类型
type FormData = z.infer<typeof formSchema>;
// 生成各模式对应的错误类型
type FclErrors = z.ZodError<z.infer<typeof fclSchema>>["formErrors"]["fieldErrors"];
type LclErrors = z.ZodError<z.infer<typeof lclSchema>>["formErrors"]["fieldErrors"];
type BulkErrors = z.ZodError<z.infer<typeof bulkSchema>>["formErrors"]["fieldErrors"];

2. 类型收窄访问动态字段错误

在渲染动态字段的条件分支里,通过类型断言或类型守卫告诉TS当前模式下对应字段的错误是存在的:

方式一:类型断言(简单直接)

const { register, handleSubmit, watch, formState: { errors } } = useForm<FormData>({
  resolver: ZodResolver(formSchema),
  defaultValues: {
    from: "",
    to: "",
    modes: "FCL",
  },
});

const currentMode = watch("modes");

return (
  <form onSubmit={handleSubmit((data) => console.log(data))}>
    {/* 公共字段 */}
    <input {...register("from")} placeholder="From" />
    {errors.from?.[0] && <p>{errors.from[0]}</p>}

    <input {...register("to")} placeholder="To" />
    {errors.to?.[0] && <p>{errors.to[0]}</p>}

    <select {...register("modes")}>
      <option value="FCL">FCL</option>
      <option value="LCL">LCL</option>
      <option value="BULK">BULK</option>
    </select>

    {/* FCL动态字段 */}
    {currentMode === "FCL" && (
      <div>
        <input type="number" {...register("f_quantity")} placeholder="FCL Quantity" />
        {(errors as FclErrors).f_quantity?.[0] && <p>{(errors as FclErrors).f_quantity[0]}</p>}

        <input type="number" {...register("f_weight")} placeholder="FCL Weight" />
        {(errors as FclErrors).f_weight?.[0] && <p>{(errors as FclErrors).f_weight[0]}</p>}
      </div>
    )}

    {/* LCL动态字段 */}
    {currentMode === "LCL" && (
      <div>
        <input type="number" {...register("l_volume")} placeholder="LCL Volume" />
        {(errors as LclErrors).l_volume?.[0] && <p>{(errors as LclErrors).l_volume[0]}</p>}
      </div>
    )}

    {/* BULK动态字段 */}
    {currentMode === "BULK" && (
      <div>
        <input type="number" {...register("b_totalWeight")} placeholder="Bulk Total Weight" />
        {(errors as BulkErrors).b_totalWeight?.[0] && <p>{(errors as BulkErrors).b_totalWeight[0]}</p>}
      </div>
    )}

    <button type="submit">Submit</button>
  </form>
);

方式二:类型守卫(更优雅)

先定义类型守卫函数,让TS自动推断错误类型:

function isFclError(errors: typeof errors): errors is FclErrors {
  return currentMode === "FCL";
}

function isLclError(errors: typeof errors): errors is LclErrors {
  return currentMode === "LCL";
}

function isBulkError(errors: typeof errors): errors is BulkErrors {
  return currentMode === "BULK";
}

使用时无需手动断言:

{currentMode === "FCL" && (
  <div>
    <input type="number" {...register("f_quantity")} placeholder="FCL Quantity" />
    {isFclError(errors) && errors.f_quantity?.[0] && <p>{errors.f_quantity[0]}</p>}
  </div>
)}

原理说明

Zod的discriminatedUnion生成的是联合类型的表单数据,但React Hook Form返回的errors类型是所有可能字段的错误联合类型。通过类型断言或守卫,我们可以根据当前选中的mode,将errors收窄为对应模式的错误类型,让TS认可动态字段的存在。

内容的提问来源于stack exchange,提问作者Amith A G

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:23:18