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

基于React Hook Form和Zod的多步骤表单实时验证问题

解决React Hook Form + Zod多步骤表单的当前步骤验证问题

核心思路

要实现当前步骤字段的独立验证,关键是拆分验证逻辑到单个步骤,不依赖全局的isValid(它对应整个表单的状态),而是通过「当前步骤字段的错误状态+交互状态」或者「手动触发步骤级验证」来控制按钮禁用状态。

具体实现方案

1. 拆分Zod Schema为步骤级

先把每个步骤的字段单独定义Schema,再合并为完整表单Schema,既支持步骤独立验证,也能保证最终提交的数据完整性:

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

// 步骤1专属Schema
const step1Schema = z.object({
  firstName: z.string().min(2, "名字至少2个字符"),
  lastName: z.string().min(2, "姓氏至少2个字符"),
});

// 步骤2专属Schema
const step2Schema = z.object({
  email: z.string().email("请输入有效的邮箱"),
  phone: z.string().regex(/^1[3-9]\d{9}$/, "请输入有效的手机号"),
});

// 完整表单Schema(用于最终提交验证)
const fullFormSchema = step1Schema.merge(step2Schema);
type FormValues = z.infer<typeof fullFormSchema>;

2. 配置表单与步骤状态

使用useState管理当前步骤,表单配置mode满足即时验证需求:

import { useState } from "react";

export default function MultiStepForm() {
  const [currentStep, setCurrentStep] = useState(1);

  const {
    register,
    formState: { errors, touchedFields },
    trigger, // 手动触发指定字段验证
    getValues, // 获取指定字段的值
  } = useForm<FormValues>({
    resolver: zodResolver(fullFormSchema),
    mode: "onTouched", // 失焦时触发验证,若要输入时即时验证改为"onChange"
  });

3. 定义当前步骤字段与验证逻辑

编写工具函数获取当前步骤的字段列表,然后判断这些字段是否全部验证通过:

// 获取当前步骤的所有字段名
const getCurrentStepFields = () => {
  switch (currentStep) {
    case 1: return ["firstName", "lastName"];
    case 2: return ["email", "phone"];
    default: return [];
  }
};

// 判断当前步骤是否满足激活下一步的条件
const isCurrentStepValid = () => {
  const currentFields = getCurrentStepFields();
  // 逻辑:用户已交互过该字段,且字段无错误
  return currentFields.every(field => touchedFields[field] && !errors[field]);
};

4. 渲染步骤与控制按钮

根据当前步骤渲染对应字段,按钮禁用状态由isCurrentStepValid控制,点击下一步时手动触发当前步骤的全量验证:

const handleNext = async () => {
  // 手动触发当前步骤所有字段的验证(即使未交互过的字段也会验证)
  const stepIsValid = await trigger(getCurrentStepFields());
  if (stepIsValid) {
    setCurrentStep(prev => prev + 1);
  }
};

return (
  <div className="multi-step-form">
    {/* 步骤1渲染 */}
    {currentStep === 1 && (
      <div className="step-content">
        <div className="form-group">
          <label>名字</label>
          <input {...register("firstName")} />
          {errors.firstName && <p className="error-text">{errors.firstName.message}</p>}
        </div>
        <div className="form-group">
          <label>姓氏</label>
          <input {...register("lastName")} />
          {errors.lastName && <p className="error-text">{errors.lastName.message}</p>}
        </div>
      </div>
    )}

    {/* 步骤2渲染 */}
    {currentStep === 2 && (
      <div className="step-content">
        <div className="form-group">
          <label>邮箱</label>
          <input {...register("email")} />
          {errors.email && <p className="error-text">{errors.email.message}</p>}
        </div>
        <div className="form-group">
          <label>手机号</label>
          <input {...register("phone")} />
          {errors.phone && <p className="error-text">{errors.phone.message}</p>}
        </div>
      </div>
    )}

    <button 
      onClick={handleNext}
      disabled={!isCurrentStepValid()}
      className="next-btn"
    >
      下一步
    </button>
  </div>
);
}

可选优化:无交互时也验证字段值

如果希望用户未交互字段,但输入值符合规则时也激活按钮,可以修改isCurrentStepValid的逻辑,直接用步骤Schema验证字段值:

const isCurrentStepValid = () => {
  const currentFields = getCurrentStepFields();
  const currentValues = Object.fromEntries(
    currentFields.map(field => [field, getValues(field)])
  );
  const currentSchema = currentStep === 1 ? step1Schema : step2Schema;
  return currentSchema.safeParse(currentValues).success;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 09:58:21