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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:55:58