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

使用Zod进行表单条件验证失效问题排查

问题分析与解决方案

问题根源

你的代码存在两个核心问题,导致Zod验证始终检查所有三个输入字段:

  1. 基础验证优先级高于refine:当前Zod Schema中,firstname、address、email都被设置为必填(z.string().min(1)),Zod会先执行这些基础规则的验证,只有基础验证通过后才会触发refine。这意味着即使你写了refine逻辑,基础验证已经强制要求所有字段非空,所以会出现“始终验证三个字段”的情况。
  2. Schema未关联单选按钮状态:你的单选按钮状态searchOption是单独的useState,没有纳入表单数据中,Zod的refine无法获取用户选中的选项,自然无法根据选择判断需要验证哪个字段。

修复方案

步骤1:修改Zod Schema

将三个输入字段改为可选,并把单选按钮的选择状态加入Schema,通过superRefine实现条件验证(superRefine比refine更灵活,可精准添加字段错误):

import { z } from "zod";

export const FormSchema = z.object({
  // 将单选按钮的选择状态纳入Schema,限定可选值
  searchOption: z.enum(["searchName", "searchAddress", "searchEmail"]),
  // 三个输入字段改为可选
  firstname: z.string().optional(),
  address: z.string().optional(),
  email: z.string().optional(),
}).superRefine((data, ctx) => {
  switch (data.searchOption) {
    case "searchName":
      // 验证姓名字段非空(trim排除纯空格)
      if (!data.firstname?.trim()) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "请输入姓名",
          path: ["firstname"], // 错误关联到姓名输入框
        });
      }
      break;
    case "searchAddress":
      if (!data.address?.trim()) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "请输入地址",
          path: ["address"],
        });
      }
      break;
    case "searchEmail":
      if (!data.email?.trim()) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "请输入邮箱",
          path: ["email"],
        });
      } else {
        // 额外验证邮箱格式
        const emailValidation = z.string().email().safeParse(data.email);
        if (!emailValidation.success) {
          ctx.addIssue({
            code: z.ZodIssueCode.invalid_string,
            validation: "email",
            message: "请输入有效的邮箱地址",
            path: ["email"],
          });
        }
      }
      break;
  }
});

export type FormSchemaType = z.infer<typeof FormSchema>;

步骤2:调整React组件

将单选按钮绑定到表单数据中,替代单独的useState,确保Zod能获取到用户的选择:

import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { FormSchema, FormSchemaType } from "./path/to/your/schema";

const SearchForm = () => {
  const methods = useForm<FormSchemaType>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      searchOption: "searchName", // 默认选中按姓名搜索
      firstname: "",
      address: "",
      email: "",
    },
  });

  const { handleSubmit, register, formState: { errors } } = methods;

  const onFormSubmit = (data: FormSchemaType) => {
    // 处理表单提交逻辑
    console.log("提交数据:", data);
  };

  return (
    <FormProvider {...methods}>
      <form onSubmit={handleSubmit(onFormSubmit)} noValidate>
        {/* 单选按钮绑定到searchOption字段 */}
        <div>
          <input
            type="radio"
            id="searchName"
            value="searchName"
            {...register("searchOption")}
          />
          <label htmlFor="searchName">按姓名搜索</label>
        </div>

        <div>
          <input
            type="radio"
            id="searchAddress"
            value="searchAddress"
            {...register("searchOption")}
          />
          <label htmlFor="searchAddress">按地址搜索</label>
        </div>

        <div>
          <input
            type="radio"
            id="searchEmail"
            value="searchEmail"
            {...register("searchOption")}
          />
          <label htmlFor="searchEmail">按邮箱搜索</label>
        </div>

        {/* 姓名输入框 */}
        <div>
          <label htmlFor="firstname">姓名</label>
          <input
            type="text"
            id="firstname"
            {...register("firstname")}
          />
          {errors.firstname && <p className="error">{errors.firstname.message}</p>}
        </div>

        {/* 地址输入框 */}
        <div>
          <label htmlFor="address">地址</label>
          <input
            type="text"
            id="address"
            {...register("address")}
          />
          {errors.address && <p className="error">{errors.address.message}</p>}
        </div>

        {/* 邮箱输入框 */}
        <div>
          <label htmlFor="email">邮箱</label>
          <input
            type="email"
            id="email"
            {...register("email")}
          />
          {errors.email && <p className="error">{errors.email.message}</p>}
        </div>

        <button type="submit">提交</button>
      </form>
    </FormProvider>
  );
};

export default SearchForm;

关键说明

  • 输入字段改为optional()后,Zod不会强制要求所有字段非空,只有当单选按钮选中对应选项时,才会通过superRefine验证该字段。
  • 将searchOption纳入表单数据,让Zod能直接获取用户的选择,从而实现条件验证逻辑。
  • 使用superRefine可以精准地将错误信息关联到对应的输入字段,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:25:47