React Hook Form 的主要特点
- 轻量高效
- 相比于其他表单库(如 Formik、Redux Form),它的包体积更小,并且在管理表单状态上拥有更好的性能(尽量减少了组件的重复渲染)。
- 不受控组件为主
- 传统受控组件通常会把输入值存储在 React state 中,并在每次输入时触发
onChange
事件来更新 state,从而引起多次渲染。 - React Hook Form 则利用原生的表单行为,尽可能将输入值存储在 DOM 中(即“非受控组件”),只有在真正需要时才与 React 同步。
- 传统受控组件通常会把输入值存储在 React state 中,并在每次输入时触发
- 易于使用和集成
- 使用
useForm
、Controller
等 Hook 简洁地创建表单。 - 提供了丰富的验证方案,可以配合
Yup
、zod
等验证库使用。
- 使用
- 良好的表单验证体验
- 内置多种验证规则(必填、最小长度、最大长度、模式匹配等)。
- 允许自定义验证规则(自定义函数或其他第三方验证库)。
- 灵活的错误处理
- 在表单提交或失焦时触发验证,并能在组件中通过
errors
对象来捕获并渲染错误信息。
- 在表单提交或失焦时触发验证,并能在组件中通过
React Hook Form 的核心 API
useForm()
- 主要 Hook,用于注册输入项、管理表单状态和验证。
- 返回
{ register, handleSubmit, formState, ... }
等对象和方法。
register
- 负责将表单字段与 React Hook Form 进行绑定。
- 用法通常是
<input {...register("fieldName", { required: true, minLength: 5 })} />
.
handleSubmit
- 用于处理表单提交,将验证逻辑集成进来。
- 例如:
onSubmit={handleSubmit((data) => console.log(data))}
。
formState
- 提供当前表单的状态,比如
errors
、isDirty
、isValid
等。
- 提供当前表单的状态,比如
Controller
(可选)- 当需要把 React Hook Form 绑定到第三方控件(如 Material-UI 组件、Ant Design 组件)时,可以用
Controller
包裹,手动定义value
、onChange
等映射。
- 当需要把 React Hook Form 绑定到第三方控件(如 Material-UI 组件、Ant Design 组件)时,可以用
简单示例
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. 提交按钮禁用
<button type="submit" disabled={!isValid || isSubmitting}>
{isSubmitting ? "提交中..." : "提交"}
</button>
避免无效提交: isValid
确保所有验证通过才允许提交,isSubmitting
避免重复提交。
✅ 2. 监控字段是否修改
if (isDirty) {
console.log("用户修改了某些字段");
}
适用场景:
- 防止误操作:如果用户修改了内容但未提交,可以弹出确认提示(如 “是否放弃修改?”)。
- 保存草稿功能:如果
isDirty
为true
,可以自动保存到本地存储。
✅ 3. 监控提交状态
if (isSubmitSuccessful) {
alert("表单提交成功!");
}
解释 register 函数
<input {...register("username", { required: "请输入用户名" })} />
拆解分析
register("username")
- 把
username
这个字段注册到 React Hook Form 的useForm()
管理的表单里。 - 这样,
React Hook Form
就知道它的value
,并能在handleSubmit
时获取它的值。
- 把
{ required: "请输入用户名" }
- 这是一个验证规则,表示这个字段是必填的,如果用户没有输入,则
errors.username
会包含"请输入用户名"
这个错误信息。
- 这是一个验证规则,表示这个字段是必填的,如果用户没有输入,则
{...register(...)}
register()
返回一个对象,其中包含onChange
、onBlur
、name
、ref
等属性。...
展开运算符 会把这些属性添加到<input>
里,让它们自动绑定。
✅ 最终作用:
value
由 React Hook Form 自动管理(而不是useState
)。onChange
由 React Hook Form 自动处理(记录用户输入)。onBlur
由 React Hook Form 自动处理(触发验证)。- 不用手动管理
state
,写表单代码更简洁!
不是说非受控吗,为啥还有onChange?
register("username")
实际上 只是告诉 React Hook Form 需要监听这个字段,但是值依然是由浏览器管理的,而不是 Reactstate
。在 React Hook Form 内部:
value
由 DOM 自己管理(非受控)。onChange
仅仅是 让 React Hook Form 知道表单数据什么时候变化。
与其他表单库的对比
- Formik
- 更偏向于受控组件的思路;
- 写起来相对直观,容易上手,但对大型表单可能造成较多渲染。
- Redux Form
- 早期流行的表单解决方案,把表单状态存储在 Redux store 中;
- 现在已经不太推荐,因为频繁更新表单导致 Redux 的性能开销较大。
- React Hook Form
- 优点:基于 React Hook,API 简单,性能好,最流行的解决方案之一;
- 缺点:在非常复杂的场景下,可能需要配合
Controller
或第三方验证库,否则需要编写更多自定义逻辑。
在 React Hook Form 中,如果你想将表单字段与 Material UI(MUI) 组件(如 TextField
、Select
、Checkbox
等)直接结合使用,最常见的做法是使用 Controller
组件来进行绑定。下面给出一个完整的示例和一些常见的用法说明。
使用 Controller
与 Material UI 结合
示例:使用 Material UI 的 TextField
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>
);
}
说明:
useForm
返回的control
对象需要传给Controller
的control
属性。Controller
的render
属性必须是一个函数,参数包含field
、fieldState
、formState
等,你可以通过展开field
来把value
,onChange
,onBlur
,ref
等关键属性注入到 MUI 组件里。- 在
TextField
上,如果有验证错误(例如必填未填,或者长度不够),可以通过error
和helperText
属性配合显示错误信息。 rules
属性中可以设置验证规则,比如required
、minLength
、maxLength
、pattern
等。如果需要更复杂的验证,可以结合 Yup 或 Zod。
常见的 Material UI 组件示例
1. Select
下拉选择
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
勾选框
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
单选框
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 组件(或第三方组件)如果支持 ref
和 onChange
,有时也能直接使用 register
而不必用 Controller
,不过大部分 MUI 组件(尤其是带有内部状态的组件)推荐使用 Controller
来映射。
小结
- 核心思路:使用
Controller
,将React Hook Form
的field
属性与 Material UI 组件的props
建立映射(value
、onChange
、ref
等)。 - 验证逻辑:
rules
中可定义常见的规则,也可配合第三方验证库。 - 错误提示:通过
errors
来决定 MUI 组件的error
和helperText
。
这样就能让 React Hook Form 和 Material UI 轻松地结合起来,实现一个功能齐全、性能良好的表单。
Yup 和 React Hook Form 结合使用
🔹 如何结合 Yup
和 React Hook Form
,以及它的好处
Yup
是一个功能强大的 JavaScript 对象模式验证库,而 React Hook Form (RHF)
是一个基于 React Hooks 的高性能表单管理库。将 Yup
与 React Hook Form
结合,可以实现灵活、高效、类型安全的表单验证。
1️⃣ 结合 Yup
和 React Hook Form
📌 基础示例:使用 Yup
进行验证
安装 yup
和 @hookform/resolvers
(RHF 解析器)
npm install yup @hookform/resolvers
代码示例
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 验证规则。- 所有错误信息自动绑定到
errors
,TextField
组件的error
和helperText
会自动显示错误信息。 - 当用户输入时,错误状态会自动更新,不需要手动编写
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]+$/, "只能包含字母和数字") |
📌 例子:更多字段验证
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. 代码简洁
- 不需要手动监听
onChange
、onBlur
,也不需要useState
存储错误信息 - 所有逻辑都在
schema
里定义,表单代码更清晰
✅ 2. 性能更好
传统表单:
tsxconst [errors, setErrors] = useState({});
每次
setErrors
都会导致组件重新渲染,影响性能。
RHF + Yup
tsxconst { formState: { errors } } = useForm();
错误状态不会影响整个组件重新渲染,提高性能。
✅ 3. 更好的扩展性
- 可以集成
Zod
、Superstruct
等其他验证库 - 可以结合
React Query
进行异步验证(如用户名是否已存在) - 可以用于动态表单,支持
append
、remove
4️⃣ 进阶:结合 async
进行异步验证
有时,我们需要检查用户名是否已经注册,这可以用 yup.test()
实现:
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 Form
的 Controller
如果你使用 Material UI 的 Select
,Checkbox
,Radio
等非原生组件,可以用 Controller
绑定:
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
组合! 🚀