详解React Hook Form 和 Yup 实现输入表单的验证

552 阅读6分钟

概要

大家好,我是Alex独开。 在本文中,我们总结了如何使用 React Hook Form 和 Yup 实现输入表单的验证。 通过结合 React Hook Form 和 Yup,即使表单中的输入项很多或验证项很复杂,也可以非常方便高效地实现。 如果你正在考虑使用 React Hook Form 和 Yup,或者你已经阅读了官方文档但不知道如何使用它,请参考这篇文章。

开发环境

  • Windows10+

  • Next.js 14.0.4

  • React 19

  • Sass 1.83.7

  • React Hook Form 7.54.2

  • @hookform/resolvers 3.9.1

  • Yup 1.6.1

  • Visual Studio Code 1.84.1

前提

这里假设你已经使用 Next.js 创建完成了项目 在这里我们不会讨论如何使用 Next.js构建环境。

安装必要的库

在您创建的 Next.js 项目中,需要添加以下三个库

  • React Hook Form

  • Yup

  • @hookform/resolvers

安装的执行命令,请参阅以下内容。

//TERMINAL
npm install react-hook-form yup @hookform/resolvers

接下来我们先简单介绍一下每个库。

  • React Hook Form ‌React Hook Form‌ 是一个基于React Hooks的表单库,旨在通过提供一系列的钩子(Hooks)来简化表单状态管理和验证。它具有高性能、轻量级、易于扩展和校验的特点,能够显著减少代码量,提高代码的可读性和可维护性‌。

  • Yup Yup‌是一个功能强大且易用的JavaScript对象模式验证库,主要用于数据验证。它以声明式的方式对数据结构进行验证,广泛应用于React、Vue等前端框架以及其他JavaScript应用中。 虽然我们也可以使用React Hook Form 实现表单的验证,但是Yup允许我们设置各种验证规则,例如必须输入检查和正则表达式检查。

  • @hookform/resolvers React Hook Form与外部验证库Yup结合使用的时候必须使用@hookform/resolvers作为纽带

使用 Yup 实现表单验证

首先,使用Yup 实现表单验证 先把表单验证的所有代码都复制出来

//src/components/form/validation.tsx
import * as yup from 'yup';

const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;

export const schema = yup.object().shape({
  fullName: yup.string().required("请输入名称"),
  email: yup.string().email("邮箱格式不正确").required("请输入邮箱"),
  password: yup.string().required("请输入密码").min(8).matches(passwordRegex, "密码包含非法字符"),
  confirmPassword: yup.string().required("请再次输入密码").oneOf([yup.ref('password'),], '两次密码输入不一致'),
});

  1. 首先,我们使用 object() 将输入表单的输入值定义为对象。 接下来,使用 shape() 指定具体的数据格式要求。
  2. 使用 required() ,则该字段为必填字段。
  3. 使用 email() ,则该字段必须采用电子邮件地址的形式。
  4. 使用 matches() , 则该字段必须符合您自己定义的正则表达式。
  5. 使用 oneOf() , 则该字段必须与您指定项目的数值匹配。 在本例中,我们结合Yup.ref()使用,以验证它是否与密码的值匹配。

此外,您还可以使用 min() 和 max() 限制输入的字符数。 这里不对所有的验证方法进行一一介绍,大家请参阅 Yup 的 GitHub查看详细内容。

用户注册界面的实现

//\app\components\input\input.module.scss
.formWrapper {
  display: flex;
  flex-direction: column;
  text-align: center;
  margin:0 auto;
  padding-left: 10%;
  padding-right: 10%;
}

.formItemWrapper{
  display: flex;
  flex-direction: column;
  text-align: start;
  padding: 24px 0;
}

.formErrorMessage{
  text-align: left;
  color: red;
}
//\app\page.tsx
"use client";
import { yupResolver } from "@hookform/resolvers/yup";
import yup from "yup";
import { useForm, SubmitHandler } from "react-hook-form";
import { schema } from "./validation";
import styles from "./form.module.scss";

// 1
type ContactFormData = yup.InferType<typeof schema>;

export const Form = () => {
  // 2
  const { register, handleSubmit, formState } = useForm<ContactFormData>({
    resolver: yupResolver(schema), // 结合Yup
    mode: "onBlur", // 指定验证表单的时机
  });
  const onSubmit: SubmitHandler<ContactFormData> = (data) => {
    // 表单检证成功,可以进行后续处理
    console.log(data);
  };
  
   // 3
  return (
    <form className={styles.formWrapper} onSubmit={handleSubmit(onSubmit)}>
      <h1>
        用户注册
      </h1>
      <div>
        请输入用户注册信息
      </div>
      <div className={styles.formItemWrapper}>
        <label>姓名</label>
        <input {...register("fullName")} />
        <span className={styles.formErrorMessage}>{formState.errors.fullName?.message}</span>
      </div>
      <div className={styles.formItemWrapper}>
        <label>邮箱地址</label>
        <input {...register("email")} />
        <span className={styles.formErrorMessage}>{formState.errors.email?.message}</span>
      </div>
      <div className={styles.formItemWrapper}>
        <label>密码</label>
        <input type="password" autoComplete="false" {...register("password")} />
        <span className={styles.formErrorMessage}>{formState.errors.password?.message}</span>
      </div>
      <div className={styles.formItemWrapper}>
        <label>密码(确认)</label>
        <input type="password" autoComplete="false" {...register("confirmPassword")} />
        <span className={styles.formErrorMessage}>{formState.errors.confirmPassword?.message}</span>
      </div>
      <div className={styles.buttonContainer}>
        <button disabled={!formState.isValid}>注册</button>
      </div>
    </form>
  );
};

至于 CSS,它与这个 input 表单验证的实现没有直接关系,所以实现保持在最低限度。 仅供大家参照。

我将按顺序解释 form.tsx 的代码。

  1. 通过使用yup.InferType,依据validation.tsx中定义的schema创建一个type类型的对象。
  2. 使用useForm定义表单的验证内容
  • 参数resolver中 yupResolver 设置为参数解析器,实现与 Yup 的联动
  • mode 参数中控制验证检查的时间。 在此示例中,设置了 onBlur,所以在输入字段失焦时执行验证检查。 有关其他设置,请参阅 useForm 文档
  1. 使用useForm中的Register 用于关联每个输入框字段的相应验证。 {... register(TFieldName)} 表示法乍一看可能会让人感到困惑,但 useForm 文档描述了调用 registerAPI 时的具体方法。 这将帮助您更好地理解。


const { onChange, onBlur, name, ref } = register('firstName'); 
// include type check against field path with the name you have supplied.
        
<input 
  onChange={onChange} // assign onChange event 
  onBlur={onBlur} // assign onBlur event
  name={name} // assign name prop
  ref={ref} // assign ref prop
/>
// same as above
// 下面的一句与上面的代码等效
<input {...register('firstName')} />

此外,通过使用 formState用于向每个输入表单显示错误消息,并且在所有输入表单都验证通过检查之前,无法按下注册按钮。

通过以上的实现,将获取与下图相同的注册页面。 在这里插入图片描述

使用 React Hook Form 处理你的自定义组件

在实际项目中,你可能需要处理一个包含 的自制组件。 如果你按按照上面的方法定义到你自定义的组件,React Hook Form 也不会正确响应。 要解决这个问题,你需要使用 forwardRef 实现组件。

举一个具体的例子。 下面的代码实际上是与上述 src/components/form/form.tsx 分开的

//\app\components\input\input.module.scss
.formItemWrapper{
  display: flex;
  flex-direction: column;
  text-align: start;
  padding: 24px 0;
}

.formErrorMessage{
  text-align: left;
  color: red;
}
//\app\components\input\input.tsx
import  { ComponentPropsWithRef, forwardRef } from "react";
import styles from "./input.module.scss";

export type InputProps = {
    labelText : string,
    errorMessage? : string,
} & ComponentPropsWithRef<"input">

export const Input = forwardRef<HTMLInputElement, InputProps>((
    {
        labelText,
        errorMessage,
        ...props
    }: InputProps,
    ref
) => {
    return (
        <div className={styles.formItemWrapper}>
            <label>{labelText}</label>
            <input ref={ref} {...props}/>
            <span className={styles.formErrorMessage}>{errorMessage}</span>
        </div>
    );
});
    
Input.displayName = 'Input';
//\app\page.tsx
"use client";
import { yupResolver } from "@hookform/resolvers/yup";
import yup from "yup";
import { useForm, SubmitHandler } from "react-hook-form";
import { schema } from "./validation";
import styles from "./form.module.scss";
import { Input } from "./components/input/input";

// 1
type ContactFormData = yup.InferType<typeof schema>;

const RegisterForm = () => {
  // 2
  const { register, handleSubmit, formState: { errors, isValid } } = useForm<ContactFormData>({
    resolver: yupResolver(schema), // 结合Yup
    mode: "onBlur", // 指定验证表单的时机
  });
  const onSubmit: SubmitHandler<ContactFormData> = (data) => {
    // 表单检证成功,可以进行后续处理
    console.log(data);
  };
  
   // 3
  return (
    <form className={styles.formWrapper} onSubmit={handleSubmit(onSubmit)}>
      <h1>
        用户注册
      </h1>
      <div>
        请输入用户注册信息
      </div>
      <Input 
        type="text" 
        labelText="姓名" 
        errorMessage={errors.fullName?.message}
        {...register("fullName")}
      />
      <Input 
        type="text" 
        labelText="邮箱地址" 
        errorMessage={errors.email?.message}
        {...register("email")}
      />
      <Input 
        type="password" 
        autoComplete="new-password"
        labelText="密码"
        errorMessage={errors.password?.message}
        {...register("password")}
      />
      <Input 
        type="password" 
        autoComplete="new-password"
        labelText="密码(确认)"
        errorMessage={errors.confirmPassword?.message}
        {...register("confirmPassword")}
      />
      <div className={styles.buttonContainer}>
        <button disabled={!isValid}>注册</button>
      </div>
    </form>
  );
};

export default RegisterForm;

现在 register 可以正常访问自组建的ref了,子组件的异常检证可以正常处理了

小结

除了本文中介绍的那些之外,还可以通过结合 React Hook Form 和 Yup 来实现具有各种验证的输入表单。 我还在学习,但我认为将来会有很多机会用输入表单实现东西,所以我想加深理解并提高开发速度。

参考站点

首页 | React Hook Form - 简单的 React 表单验证 www.react-hook-form.com/

Yup github.com/jquense/yup

react-hook-form/resolvers github.com/react-hook-…

React react.dev/