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

如何用Zod动态生成Schema?React Hook Form场景需求

动态生成Zod Schema实现方案

要实现从字段数组动态生成Zod Schema,同时保留TypeScript类型推断以适配react-hook-form的useForm,可以按以下步骤操作:

1. 定义字段类型与生成函数

首先明确表单字段的结构,再编写生成函数将字段数组转换为Zod Object Schema:

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

// 定义表单字段的通用类型
type FormField<T extends z.ZodTypeAny> = {
  name: string;
  fieldType: T;
  // 可扩展其他属性:label、className、placeholder等
};

// 动态生成Zod Schema的核心函数
const generateSchemaFromFields = <T extends readonly FormField<any>[]>(fields: T) => {
  // 遍历字段数组,组装成Zod对象所需的键值对
  const schemaEntries = fields.map(field => [field.name, field.fieldType] as const);
  // 转换为Zod Object
  return z.object(Object.fromEntries(schemaEntries)) as z.ZodObject<{
    // 通过泛型推断每个字段的类型
    [K in T[number]["name"]]: Extract<T[number], { name: K }>["fieldType"];
  }>;
};

2. 使用示例

定义静态字段数组

注意要添加as const,让TypeScript捕获每个字段的具体字面量类型:

const formFields = [
  {
    name: "foo",
    fieldType: z.string().min(1, "Foo不能为空"),
    label: "Foo输入框",
    className: "form-control"
  },
  {
    name: "bar",
    fieldType: z.number().int().positive("Bar必须是正整数"),
    label: "Bar输入框",
    className: "form-control"
  }
] as const;

生成Schema并推断类型

// 生成Zod Schema
const formSchema = generateSchemaFromFields(formFields);

// 推断表单字段的TypeScript类型
type FormValues = z.infer<typeof formSchema>;
// 类型结果:{ foo: string; bar: number; }

配合react-hook-form使用

const FormComponent = () => {
  const { register, handleSubmit, formState: { errors } } = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      foo: "",
      bar: 0
    }
  });

  const onSubmit = (data: FormValues) => {
    console.log("表单数据:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {formFields.map(field => (
        <div key={field.name} className="form-group">
          <label>{field.label}</label>
          <input
            {...register(field.name as keyof FormValues)}
            className={`${field.className} ${errors[field.name] ? "is-invalid" : ""}`}
            type={field.fieldType instanceof z.ZodNumber ? "number" : "text"}
          />
          {errors[field.name] && <span className="invalid-feedback">{errors[field.name]?.message}</span>}
        </div>
      ))}
      <button type="submit">提交</button>
    </form>
  );
};

关键注意事项

  • as const的必要性:确保TypeScript能识别字段数组中每个name和fieldType的具体类型,而非宽泛的string或ZodTypeAny,否则类型推断会失效。
  • 字段唯一性:需保证字段数组中name值唯一,否则生成Schema时后面的字段会覆盖前面的。
  • 类型断言的作用:函数中的类型断言是为了让Zod正确关联字段名与对应的校验规则,确保z.infer能得到准确的表单值类型。

内容的提问来源于stack exchange,提问作者Florian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:22:54