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
相关产品推荐
相关产品推荐

