react-hook-form搭配Zod Schema:如何将onSubmit数据类型设为z.output
React Hook Form 结合 Zod Transform 时的 TypeScript 类型不匹配问题解决
问题说明
当使用带有transform方法的Zod Schema配合react-hook-form时,handleSubmit传递给onSubmit函数的数据类型始终是Zod Schema的输入类型(z.input<typeof EmailOrPhoneSchema>),而非转换后的输出类型(z.output<typeof EmailOrPhoneSchema>)。尽管实际提交的数据结构符合转换后的预期,但TypeScript类型推断错误,导致访问转换后的属性(如isEmail、value)时出现类型报错。
原因分析
默认情况下,zodResolver不会自动将Zod的transform结果传递给react-hook-form;同时如果显式指定useForm的泛型为输入类型,TypeScript会强制onSubmit的参数为该类型,忽略转换后的结构。
解决方案
需要启用zodResolver的transform选项,让resolver将转换后的数据传递给onSubmit,并让TypeScript正确推断类型。
修改后的表单代码
import React from "react"; import { Label } from "../ui/label"; import { Input } from "../ui/input"; import SubmitButton from "../small/submit-button"; import { useForm } from "react-hook-form"; import { z } from "zod"; import EmailOrPhoneSchema from "@/schemas/shared/email-or-phone"; import { zodResolver } from "@hookform/resolvers/zod"; type Props = {}; export default function ForgotPasswordForm({}: Props) { // 启用zodResolver的transform选项,让resolver返回转换后的数据 const { register, formState: { errors }, handleSubmit, } = useForm({ resolver: zodResolver(EmailOrPhoneSchema, { transform: true }), }); // TypeScript现在会自动推断data为z.output<typeof EmailOrPhoneSchema> function onSubmit(data: z.output<typeof EmailOrPhoneSchema>) { console.log(data); // 可安全访问data.isEmail、data.isPhone、data.value,无类型报错 } return ( <form onSubmit={handleSubmit(onSubmit)}> <div className="space-y-2"> <Label htmlFor="email-phone">Email or Phone</Label> <Input {...register("emailOrPhone")} id="email-phone" type="text" placeholder="Enter your email or phone number" required /> {errors.emailOrPhone && ( <span className="text-red-500 text-xs"> {errors.emailOrPhone.message} </span> )} </div> <SubmitButton type="submit" className="w-full mt-4"> Reset Password </SubmitButton> </form> ); }
额外优化:精简Zod Schema重复逻辑
你的Schema中refine和transform部分重复了邮箱/手机号的判断逻辑,可提取为复用函数减少冗余:
import { z } from "zod"; const phoneRegex = /^\+?[1-9]\d{1,14}$/; // 提取复用判断函数 const validateEmail = (value: string) => z.string().email().safeParse(value).success; const validatePhone = (value: string) => phoneRegex.test(value); const EmailOrPhoneSchema = z.object({ emailOrPhone: z .string() .trim() .refine( (value) => validateEmail(value) || validatePhone(value), { message: "Invalid email or phone number", } ), }) .transform(({ emailOrPhone }) => { const isEmail = validateEmail(emailOrPhone); return { isEmail, isPhone: validatePhone(emailOrPhone), value: isEmail ? emailOrPhone.toLowerCase() : emailOrPhone.replace(/\D/g, ""), }; }); export default EmailOrPhoneSchema;
内容的提问来源于stack exchange,提问作者Gerison
相关产品推荐
相关产品推荐

