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

React结合Zod校验的多步骤表单TypeScript类型问题解决

React多步骤表单TypeScript类型错误问题

我正在开发首个带分步客户端校验的React多步骤表单,提交到服务器前必须完成每一步校验。目前已实现一个方案,但遇到了TypeScript相关问题:useForm返回的errors对象无法正确识别类型,比如访问errors.hasMedicalCondition.message时会触发类型错误,而且自动补全功能也失效了。

现有代码

import { useState } from "react";
import { useForm, zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const personalSchema = z.object({
  dateOfBirth: z.string().min(1, "Date of birth is required"),
  phoneNumber: z.string().min(5, "Phone number is required"),
  streetAddress: z.string().min(1, "Street address is required"),
  city: z.string().min(1, "City is required"),
  emergencyContactName: z.string().min(1, "Name is required"),
  emergencyContactPhone: z.string().min(5, "Phone number is required"),
  emergencyContactRelation: z.string().min(1, "Relation is required"),
  experienceLevel: z.enum(["none", "beginner", "intermediate", "advanced"], {
    required_error: "Experience Level is required",
  }),
  experienceDescription: z.string().optional(),
});

const medicalSchema = z.object({
  hasMedicalCondition: z.enum(["yes", "no"], { message: "please select yes or no" }),
  consent: z.enum(["true"], { errorMap: () => ({ message: "You must agree to continue" }) }),
});

const formSchemas: Record<number, ZodObject<ZodRawShape>> = {
  1: personalSchema,
  2: medicalSchema,
};

const OnboardPage = () => {
  const [step, setStep] = useState<1 | 2>(1);
  const [formData, setFormData] = useState({});

  const {
    register,
    control,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(formSchemas[step]),
    mode: "onSubmit",
    defaultValues:
      step === 1
        ? {
            dateOfBirth: "",
            phoneNumber: "",
            streetAddress: "",
            city: "",
            emergencyContactName: "",
            emergencyContactPhone: "",
            emergencyContactRelation: "",
            experienceLevel: undefined,
            experienceDescription: "",
          }
        : { hasMedicalCondtion: "", consent: "" },
  });

  const handleNext = (data: any) => {
    setFormData((prev) => ({ ...prev, ...data }));
    console.log("step 1 data", formData);
    setStep(2);
  };

  const handleFinalSubmit = async (data: any) => {
    console.log("current data", data);
    const fullFormData = { ...formData, ...data };
    console.log("sending full data", fullFormData);
  };

  // 渲染表单逻辑
};

解决方法

1. 从Zod Schema生成明确的TypeScript类型

利用Zod的z.infer工具生成对应表单数据的类型,让TypeScript能精准推断结构:

// 从每个步骤的Schema生成类型
type PersonalFormData = z.infer<typeof personalSchema>;
type MedicalFormData = z.infer<typeof medicalSchema>;
// 完整表单数据类型
type FullFormData = PersonalFormData & MedicalFormData;

2. 修正formSchemas的类型定义

原定义Record<number, ZodObject<ZodRawShape>>会丢失具体Schema的类型信息,改用as const保留字面量类型:

const formSchemas = {
  1: personalSchema,
  2: medicalSchema,
} as const;

3. 为useForm指定动态泛型参数

根据当前步骤推断对应的表单类型,让errors对象获得正确的类型提示:

// 工具类型:根据步骤获取对应表单类型
type FormDataForStep<T extends keyof typeof formSchemas> = z.infer<typeof formSchemas[T]>;

const {
  register,
  control,
  handleSubmit,
  formState: { errors }, // 现在errors会根据当前步骤自动推断类型,支持自动补全
} = useForm<FormDataForStep<typeof step>>({
  resolver: zodResolver(formSchemas[step]),
  mode: "onSubmit",
  defaultValues: step === 1 
    ? ({
        dateOfBirth: "",
        phoneNumber: "",
        streetAddress: "",
        city: "",
        emergencyContactName: "",
        emergencyContactPhone: "",
        emergencyContactRelation: "",
        experienceLevel: undefined,
        experienceDescription: "",
      } satisfies PersonalFormData) // 确保默认值符合类型
    : ({
        hasMedicalCondition: "", // 修复原代码的拼写错误:hasMedicalCondtion → hasMedicalCondition
        consent: "",
      } satisfies MedicalFormData),
});

4. 修复提交函数的类型

替换any为明确的类型,避免类型丢失:

const handleNext = (data: PersonalFormData) => {
  setFormData((prev) => ({ ...prev, ...data }));
  setStep(2);
};

const handleFinalSubmit = async (data: MedicalFormData) => {
  const fullFormData: FullFormData = { ...formData, ...data } as FullFormData;
  console.log("sending full data", fullFormData);
};

5. 优化formData的类型

将初始useState({})改为部分完整表单类型:

const [formData, setFormData] = useState<Partial<FullFormData>>({});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:06:03