Skip to content

React Hook Form 的主要特点

  1. 轻量高效
    • 相比于其他表单库(如 Formik、Redux Form),它的包体积更小,并且在管理表单状态上拥有更好的性能(尽量减少了组件的重复渲染)。
  2. 不受控组件为主
    • 传统受控组件通常会把输入值存储在 React state 中,并在每次输入时触发 onChange 事件来更新 state,从而引起多次渲染。
    • React Hook Form 则利用原生的表单行为,尽可能将输入值存储在 DOM 中(即“非受控组件”),只有在真正需要时才与 React 同步。
  3. 易于使用和集成
    • 使用 useFormController 等 Hook 简洁地创建表单。
    • 提供了丰富的验证方案,可以配合 Yupzod 等验证库使用。
  4. 良好的表单验证体验
    • 内置多种验证规则(必填、最小长度、最大长度、模式匹配等)
    • 允许自定义验证规则(自定义函数或其他第三方验证库)。
  5. 灵活的错误处理
    • 在表单提交或失焦时触发验证,并能在组件中通过 errors 对象来捕获并渲染错误信息。

React Hook Form 的核心 API

  1. useForm()
    • 主要 Hook,用于注册输入项、管理表单状态和验证
    • 返回 { register, handleSubmit, formState, ... } 等对象和方法。
  2. register
    • 负责将表单字段与 React Hook Form 进行绑定。
    • 用法通常是 <input {...register("fieldName", { required: true, minLength: 5 })} />.
  3. handleSubmit
    • 用于处理表单提交,将验证逻辑集成进来。
    • 例如:onSubmit={handleSubmit((data) => console.log(data))}
  4. formState
    • 提供当前表单的状态,比如 errorsisDirtyisValid 等。
  5. Controller (可选)
    • 当需要把 React Hook Form 绑定到第三方控件(如 Material-UI 组件、Ant Design 组件)时,可以用 Controller 包裹,手动定义 valueonChange 等映射。

简单示例

jsx
import React from "react";
import { useForm } from "react-hook-form";

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  // 表单提交成功后回调
  const onSubmit = (data) => {
    console.log("表单数据:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        用户名:
        <input
          {...register("username", { required: "请输入用户名" })}
          placeholder="请输入用户名"
        />
      </label>
      {/* 错误提示 */}
      {errors.username && <p>{errors.username.message}</p>}

      <label>
        密码:
        <input
          type="password"
          {...register("password", {
            required: "请输入密码",
            minLength: {
              value: 6,
              message: "密码长度不能少于6位",
            },
          })}
          placeholder="请输入密码"
        />
      </label>
      {/* 错误提示 */}
      {errors.password && <p>{errors.password.message}</p>}

      <button type="submit">登录</button>
    </form>
  );
}

export default App;
  • 在这里,register 会把输入框绑定到 React Hook Form 的内部管理。

  • errors 对象会告诉你每个字段验证是否通过,以及对应的错误信息。

formState 的属性

formState 主要用于获取表单的状态信息,不仅限于 errors,还包括:

  • 字段修改状态 (isDirty, dirtyFields)
  • 提交状态 (isSubmitting, isSubmitted, isSubmitSuccessful)
  • 验证状态 (isValid, isValidating)
  • 交互状态 (touchedFields)
  • 提交次数 (submitCount)

1. 提交按钮禁用

jsx
<button type="submit" disabled={!isValid || isSubmitting}>
  {isSubmitting ? "提交中..." : "提交"}
</button>

避免无效提交: isValid 确保所有验证通过才允许提交,isSubmitting 避免重复提交。


2. 监控字段是否修改

jsx
if (isDirty) {
  console.log("用户修改了某些字段");
}

适用场景:

  • 防止误操作:如果用户修改了内容但未提交,可以弹出确认提示(如 “是否放弃修改?”)。
  • 保存草稿功能:如果 isDirtytrue,可以自动保存到本地存储。

3. 监控提交状态

jsx
if (isSubmitSuccessful) {
  alert("表单提交成功!");
}

解释 register 函数

jsx
<input {...register("username", { required: "请输入用户名" })} />

拆解分析

  1. register("username")
    • username 这个字段注册到 React Hook Form 的 useForm() 管理的表单里。
    • 这样,React Hook Form 就知道它的 value,并能在 handleSubmit 时获取它的值。
  2. { required: "请输入用户名" }
    • 这是一个验证规则,表示这个字段是必填的,如果用户没有输入,则 errors.username 会包含 "请输入用户名" 这个错误信息。
  3. {...register(...)}
    • register() 返回一个对象,其中包含 onChangeonBlurnameref 等属性
    • ... 展开运算符 会把这些属性添加到 <input> 里,让它们自动绑定。

最终作用

  • value 由 React Hook Form 自动管理(而不是 useState)。
  • onChange 由 React Hook Form 自动处理(记录用户输入)。
  • onBlur 由 React Hook Form 自动处理(触发验证)。
  • 不用手动管理 state,写表单代码更简洁!

不是说非受控吗,为啥还有onChange?

register("username") 实际上 只是告诉 React Hook Form 需要监听这个字段,但是值依然是由浏览器管理的,而不是 React state

在 React Hook Form 内部:

  • valueDOM 自己管理(非受控)。
  • onChange 仅仅是 让 React Hook Form 知道表单数据什么时候变化

与其他表单库的对比

  • Formik
    • 更偏向于受控组件的思路;
    • 写起来相对直观,容易上手,但对大型表单可能造成较多渲染。
  • Redux Form
    • 早期流行的表单解决方案,把表单状态存储在 Redux store 中;
    • 现在已经不太推荐,因为频繁更新表单导致 Redux 的性能开销较大。
  • React Hook Form
    • 优点:基于 React Hook,API 简单,性能好,最流行的解决方案之一;
    • 缺点:在非常复杂的场景下,可能需要配合 Controller 或第三方验证库,否则需要编写更多自定义逻辑。

React Hook Form 中,如果你想将表单字段与 Material UI(MUI) 组件(如 TextFieldSelectCheckbox 等)直接结合使用,最常见的做法是使用 Controller 组件来进行绑定。下面给出一个完整的示例和一些常见的用法说明。

使用 Controller 与 Material UI 结合

示例:使用 Material UI 的 TextField

jsx
import React from "react";
import { useForm, Controller } from "react-hook-form";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";

export default function MyForm() {
  const {
    handleSubmit,
    control,
    formState: { errors },
  } = useForm({
    defaultValues: {
      username: "",
      password: "",
    },
  });

  const onSubmit = (data) => {
    console.log("表单数据:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* 用户名 */}
      <Controller
        name="username"
        control={control}
        rules={{ required: "用户名为必填项" }}
        render={({ field }) => (
          <TextField
            {...field} // 将 field 中的 onChange, value, onBlur 等绑定到 TextField
            label="用户名"
            variant="outlined"
            error={!!errors.username}
            helperText={errors.username ? errors.username.message : ""}
            fullWidth
            margin="normal"
          />
        )}
      />

      {/* 密码 */}
      <Controller
        name="password"
        control={control}
        rules={{
          required: "密码为必填项",
          minLength: { value: 6, message: "密码至少6位" },
        }}
        render={({ field }) => (
          <TextField
            {...field}
            type="password"
            label="密码"
            variant="outlined"
            error={!!errors.password}
            helperText={errors.password ? errors.password.message : ""}
            fullWidth
            margin="normal"
          />
        )}
      />

      <Button type="submit" variant="contained" color="primary">
        提交
      </Button>
    </form>
  );
}

说明:

  1. useForm 返回的 control 对象需要传给 Controllercontrol 属性。
  2. Controllerrender 属性必须是一个函数,参数包含 fieldfieldStateformState 等,你可以通过展开 field 来把 value, onChange, onBlur, ref 等关键属性注入到 MUI 组件里。
  3. TextField 上,如果有验证错误(例如必填未填,或者长度不够),可以通过 errorhelperText 属性配合显示错误信息。
  4. rules 属性中可以设置验证规则,比如 requiredminLengthmaxLengthpattern 等。如果需要更复杂的验证,可以结合 YupZod

常见的 Material UI 组件示例

1. Select 下拉选择

jsx
import { FormControl, InputLabel, Select, MenuItem } from "@mui/material";

<Controller
  name="gender"
  control={control}
  defaultValue=""
  rules={{ required: "请选择性别" }}
  render={({ field }) => (
    <FormControl fullWidth margin="normal" error={!!errors.gender}>
      <InputLabel id="gender-select-label">性别</InputLabel>
      <Select
        labelId="gender-select-label"
        label="性别"
        {...field}
      >
        <MenuItem value="male">男</MenuItem>
        <MenuItem value="female">女</MenuItem>
      </Select>
      {errors.gender && <p style={{ color: "red" }}>{errors.gender.message}</p>}
    </FormControl>
  )}
/>

2. Checkbox 勾选框

jsx
import { FormControlLabel, Checkbox } from "@mui/material";

<Controller
  name="agree"
  control={control}
  defaultValue={false}
  rules={{ required: "请勾选同意条款" }}
  render={({ field }) => (
    <FormControlLabel
      label="我已阅读并同意相关条款"
      control={<Checkbox {...field} checked={field.value} />}
    />
  )}
/>

3. Radio 单选框

jsx
import { FormControl, FormLabel, RadioGroup, FormControlLabel, Radio } from "@mui/material";

<Controller
  name="role"
  control={control}
  defaultValue=""
  rules={{ required: "请选择角色" }}
  render={({ field }) => (
    <FormControl component="fieldset" margin="normal" error={!!errors.role}>
      <FormLabel component="legend">角色</FormLabel>
      <RadioGroup {...field}>
        <FormControlLabel value="admin" control={<Radio />} label="管理员" />
        <FormControlLabel value="member" control={<Radio />} label="会员" />
      </RadioGroup>
      {errors.role && <p style={{ color: "red" }}>{errors.role.message}</p>}
    </FormControl>
  )}
/>

如果你的控件本身支持 inputRef / onChange...

对于一些更简单的 MUI 组件(或第三方组件)如果支持 refonChange,有时也能直接使用 register 而不必用 Controller,不过大部分 MUI 组件(尤其是带有内部状态的组件)推荐使用 Controller 来映射。


小结

  • 核心思路:使用 Controller,将 React Hook Formfield 属性与 Material UI 组件的 props 建立映射(valueonChangeref 等)。
  • 验证逻辑rules 中可定义常见的规则,也可配合第三方验证库。
  • 错误提示:通过 errors 来决定 MUI 组件的 errorhelperText

这样就能让 React Hook FormMaterial UI 轻松地结合起来,实现一个功能齐全、性能良好的表单。

Yup 和 React Hook Form 结合使用

🔹 如何结合 YupReact Hook Form,以及它的好处

Yup 是一个功能强大的 JavaScript 对象模式验证库,而 React Hook Form (RHF) 是一个基于 React Hooks 的高性能表单管理库。将 YupReact Hook Form 结合,可以实现灵活、高效、类型安全的表单验证


1️⃣ 结合 YupReact Hook Form

📌 基础示例:使用 Yup 进行验证

安装 yup@hookform/resolvers(RHF 解析器)

bash
npm install yup @hookform/resolvers

代码示例

tsx
import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; // RHF 解析器
import * as yup from "yup"; // Yup 验证库
import { TextField, Button } from "@mui/material";

// 定义 Yup 验证规则
const schema = yup.object().shape({
  username: yup.string().required("用户名不能为空").min(3, "至少3个字符"),
  email: yup.string().email("请输入有效的邮箱").required("邮箱不能为空"),
  password: yup.string().min(6, "密码至少6位").required("密码不能为空"),
});

const MyForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(schema), // 使用 Yup 作为验证解析器
  });

  const onSubmit = (data) => console.log("表单数据:", data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* 用户名 */}
      <TextField
        {...register("username")}
        label="用户名"
        error={!!errors.username}
        helperText={errors.username?.message}
        fullWidth
      />

      {/* 邮箱 */}
      <TextField
        {...register("email")}
        label="邮箱"
        error={!!errors.email}
        helperText={errors.email?.message}
        fullWidth
      />

      {/* 密码 */}
      <TextField
        {...register("password")}
        type="password"
        label="密码"
        error={!!errors.password}
        helperText={errors.password?.message}
        fullWidth
      />

      <Button type="submit" variant="contained" color="primary">
        提交
      </Button>
    </form>
  );
};

export default MyForm;

✅ 这段代码做了什么?

  • yupResolver(schema) 让 RHF 自动解析 Yup 验证规则
  • 所有错误信息自动绑定到 errorsTextField 组件的 errorhelperText 会自动显示错误信息。
  • 当用户输入时,错误状态会自动更新,不需要手动编写 useState 逻辑。

2️⃣ Yup 的更多验证规则

📌 常见规则

规则用法示例
必填.required("必填项")yup.string().required("用户名不能为空")
最小长度.min(值, "错误信息")yup.string().min(6, "密码至少6位")
最大长度.max(值, "错误信息")yup.string().max(20, "不能超过20位")
邮箱格式.email("错误信息")yup.string().email("邮箱格式不正确")
数字范围.min(值).max(值)yup.number().min(18, "年龄至少18岁")
正则表达式.matches(/正则/, "错误信息")yup.string().matches(/^[a-zA-Z0-9]+$/, "只能包含字母和数字")

📌 例子:更多字段验证

tsx
const schema = yup.object().shape({
  username: yup.string().required("请输入用户名").min(3, "用户名至少3个字符"),
  email: yup.string().email("请输入正确的邮箱格式").required("请输入邮箱"),
  password: yup
    .string()
    .min(6, "密码至少6位")
    .matches(/[A-Z]/, "密码必须包含至少一个大写字母")
    .required("请输入密码"),
  age: yup.number().min(18, "年龄必须大于 18 岁").required("请输入年龄"),
  confirmPassword: yup
    .string()
    .oneOf([yup.ref("password")], "两次输入的密码必须一致")
    .required("请确认密码"),
});

3️⃣ Yup + React Hook Form 的优势

✅ 1. 代码简洁

  • 不需要手动监听 onChangeonBlur,也不需要 useState 存储错误信息
  • 所有逻辑都在 schema 里定义,表单代码更清晰

✅ 2. 性能更好

  • 传统表单:

    tsx
    const [errors, setErrors] = useState({});

    每次

    setErrors

    都会导致组件重新渲染,影响性能。

  • RHF + Yup

    tsx
    const { formState: { errors } } = useForm();

    错误状态不会影响整个组件重新渲染,提高性能。

✅ 3. 更好的扩展性

  • 可以集成 ZodSuperstruct 等其他验证库
  • 可以结合 React Query 进行异步验证(如用户名是否已存在)
  • 可以用于动态表单,支持 appendremove

4️⃣ 进阶:结合 async 进行异步验证

有时,我们需要检查用户名是否已经注册,这可以用 yup.test() 实现:

tsx
const schema = yup.object().shape({
  username: yup
    .string()
    .required("请输入用户名")
    .test("checkUsername", "用户名已被占用", async (value) => {
      const res = await fetch(`/api/check-username?username=${value}`);
      const data = await res.json();
      return data.available; // 服务器返回 { available: true/false }
    }),
});

当用户输入 username 后,Yup 会自动调用后端接口进行验证!


5️⃣ 结合 React Hook FormController

如果你使用 Material UI 的 SelectCheckboxRadio 等非原生组件,可以用 Controller 绑定:

tsx
import { Controller } from "react-hook-form";
import { Select, MenuItem } from "@mui/material";

<Controller
  name="gender"
  control={control}
  rules={{ required: "请选择性别" }}
  render={({ field }) => (
    <Select {...field}>
      <MenuItem value="male">男</MenuItem>
      <MenuItem value="female">女</MenuItem>
    </Select>
  )}
/>;

🎯 结论

Yup 让表单验证 更直观,所有规则都在 schema 里定义 ✅ React Hook Form 让表单 性能更好,不会因 setState 重新渲染 ✅ 两者结合,可以实现 强大、灵活、易维护的表单验证 🚀

如果你的表单复杂,强烈推荐 React Hook Form + Yup 组合! 🚀