react-hook-form 快速入门:基于 React Hooks 的表单状态管理与验证实战指南
【免费下载链接】react-hook-form📋 React Hooks for form state management and validation (Web + React Native)项目地址: https://gitcode.com/gh_mirrors/re/react-hook-form
本文基于 docs/README.ar-AR.md(react-hook-form 阿拉伯语社区维护版 README)的核心内容展开,面向需要快速上手表单状态管理、原生表单验证与 schema 校验集成的 React 开发者。读完本文,你将掌握useForm的最小可用流程(register/handleSubmit/errors)、全部内置验证规则及其类型定义,并了解如何对接 Yup、Zod 等第三方校验器,同时结合仓库源码与端到端测试理解验证模式的底层行为。
一、核心特性一览
react-hook-form 是一个以 React Hooks 为基石的表单状态管理与验证库,原文档将其设计目标概括为三点:性能(performance)、用户体验(UX)与开发体验(DX)。围绕这三大目标,其核心特性包括:
- 拥抱原生 HTML 表单验证:
required、pattern、min、max等规则直接映射浏览器原生校验语义,并允许通过配置启用/禁用原生校验提示; - 与 UI 组件库开箱即用集成:通过
Controller/useController包装任意受控组件,使其接入统一的表单状态管理; - 体积小、零运行时依赖:
package.json中只有devDependencies与peerDependencies,没有任何运行时dependencies,且 bundlewatch 配置 将打包产物(dist/index.cjs.js)的目标体积约束在 15.0 kB 以内(该配置为压缩产物上限,具体数值随构建方式略有差异); - 丰富的第三方校验器支持:原生支持 Yup、Zod、AJV、Superstruct、Joi、Vest、class-validator、io-ts、nope 以及自定义校验 resolver。
从源码角度看,以上能力统一由 src/useForm.ts 暴露的useFormhook 提供——它内部通过createFormControl(见 src/logic/createFormControl.ts)创建表单控制实例,并基于订阅机制实现状态分发与按需渲染,这也是其性能设计的关键(详见下文“源码视角”一节)。仓库对外导出的全部 API 集中在 src/index.ts,涵盖useForm、useFieldArray、useWatch、useFormContext、Controller、ErrorMessage等。
二、安装
原文档给出的安装命令为:
npm install react-hook-form当前仓库package.json中版本为7.88.0(见 package.json),并声明了模块入口:main指向 CJS 产物、module指向 ESM 产物,同时通过exports字段为import/require/react-server环境分别提供对应产物(package.json)。
需要关注的两个环境前提:
- React 版本:
peerDependencies声明为react: "^16.8.0 || ^17 || ^18 || ^19"(package.json),即要求 React 16.8+(Hooks 引入版本),兼容 React 17/18/19; - Node 版本:
engines声明node >= 18.0.0(package.json),使用现代工具链时需满足该版本要求。
如果使用 pnpm 或 yarn,命令对应替换为pnpm add react-hook-form或yarn add react-hook-form即可。
三、快速开始:最小可用示例
原文档提供了一段可直接运行的快速入门示例,这是理解 react-hook-form 核心用法的关键代码,下面完整复现并逐行解读:
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)}> <input {...register('firstName')} /> <input {...register('lastName', { required: true })} /> {errors.lastName && <p>Last name is required.</p>} <input {...register('age', { pattern: /\d+/ })} /> {errors.age && <p>Please enter a number for age.</p>} <input type="submit" /> </form> ); }这段代码展示了四个核心概念:
useForm():创建表单控制实例,返回register、handleSubmit与formState。从源码看,useForm内部通过React.useRef缓存表单控制实例,保证重渲染时复用同一控制对象(src/useForm.ts)。register(name, options?):注册字段。name支持点路径(如'user.firstName')与数组索引(如'items[0].name')嵌套结构;第二个参数传入验证规则对象。handleSubmit(onSubmit):绑定到<form>的onSubmit。它会先执行全表单验证,通过后才调用传入的onSubmit(data)回调,回调参数为规范化后的表单数据;验证失败时则不会触发提交回调。formState.errors:按字段名存储验证错误的对象,errors.lastName在对应字段校验失败时存在,可直接驱动 UI 渲染错误提示。
仓库中的完整可运行版本见 app/src/basic.tsx,它覆盖了嵌套字段(nestItem.nest1)、数组字段(arrayItem.0.test1)、单选、复选、下拉框与多选框等几乎全部原生控件类型;V7 目录下另有更精简的入门示例 examples/V7/basic.tsx。
3.1 register 验证选项的完整类型
register的第二个参数在类型层面定义为RegisterOptions(见 src/types/validator.ts),完整选项如下:
| 选项 | 类型 | 说明 |
|---|---|---|
required | Message \| ValidationRule<boolean> | 必填校验;可传布尔值或{ value, message }自定义错误消息 |
min | ValidationRule<number \| string> | 最小值/最小日期(字符串日期同样适用) |
max | ValidationRule<number \| string> | 最大值/最大日期 |
minLength | ValidationRule<number> | 最小长度 |
maxLength | ValidationRule<number> | 最大长度 |
pattern | ValidationRule<RegExp> | 正则匹配 |
validate | Validate \| Record<string, Validate> | 自定义校验函数(支持异步与返回 Promise),或为多个校验规则命名 |
value | FieldPathValue | 预设字段值 |
setValueAs | (value: any) => any | 值转换函数,在存储前对原始输入做格式化 |
shouldUnregister | boolean | 字段卸载时是否注销 |
onChange/onBlur | (event) => void | 事件钩子 |
disabled | boolean | 禁用字段 |
deps | FieldPath \| FieldPath[] | 依赖字段,依赖变化时触发本字段重新验证 |
valueAsNumber | boolean | 将输入转换为number类型存储 |
valueAsDate | boolean | 将输入转换为Date类型存储 |
其中ValidationRule<T>既接受原始值(如required: true、min: 10),也接受{ value, message }对象形式用于自定义错误消息(src/types/validator.ts)。
3.2 验证规则的执行顺序
内置规则的执行顺序由 src/constants.ts 中的INPUT_VALIDATION_RULES常量确定:max → min → maxLength → minLength → pattern → required → validate。即先校验数值/长度边界类规则,再校验必填与正则,最后执行自定义validate函数——validate永远拥有最终决定权。
四、内置验证规则详解与示例
原文档快速示例中仅用到required与pattern,而仓库 app/src/basic.tsx 提供了全部规则的实战写法,以下逐一展开:
// 必填 + 最大长度(字符串数字混用) <input {...register('lastName', { required: true, maxLength: 5 })} /> // 数值范围(type="number") <input type="number" {...register('min', { min: 10 })} /> <input type="number" {...register('max', { max: 20 })} /> // 日期范围(type="date",字符串比较) <input type="date" {...register('minDate', { min: '2019-08-01' })} /> <input type="date" {...register('maxDate', { max: '2019-08-01' })} /> // 长度校验 <input {...register('minLength', { minLength: 2 })} /> // 正则校验 <input {...register('pattern', { pattern: /\d+/ })} /> // 自定义校验:返回 false 表示不通过,返回字符串可作为错误消息 <input {...register('validate', { validate: (value) => value === 'test', })} />4.1 单选框、复选框与多选框
同一name注册多个 radio 即构成单选组;checkbox 可单独注册(值为true/false),也可多个同name注册形成数组值;<select multiple>则收集选中项为数组:
// 单选组 <input type="radio" {...register('radio', { required: true })} value="1" /> <input type="radio" {...register('radio')} value="2" /> // 复选数组 <input type="checkbox" value="1" {...register('checkboxArray', { required: true })} /> <input type="checkbox" value="2" {...register('checkboxArray', { required: true })} /> // 多选下拉 <select multiple {...register('multiple', { required: true })}> <option value="optionA">optionA</option> <option value="optionB">optionB</option> </select>上述控件在提交时会由内部逻辑(getRadioValue.ts、getCheckboxValue.ts)分别聚合为单一值或数组,最终提交的数据结构与 e2e 测试中的断言一致(见 e2e/basic.spec.ts:radio: '1'、checkboxArray: ['3']、multiple: ['optionA', 'optionB'])。
4.2 嵌套字段与数组字段
register的name天然支持路径表达式:
<input {...register('nestItem.nest1', { required: true })} /> {errors.nestItem?.nest1 && <p>nest 1 error</p>} <input {...register('arrayItem.0.test1', { required: true })} /> {errors.arrayItem?.[0]?.test1 && <p>array item 1 error</p>}错误对象同样按路径嵌套,读取时需使用可选链避免深层路径访问报错。字段值类型定义在 src/types/fields.ts,其路径类型基于 src/types/path 实现,可在 TypeScript 下获得完整的字段名类型提示。
五、接入第三方 Schema 校验器
原文档明确列出对Yup、Zod、AJV、Superstruct、Joi、Vest、class-validator、io-ts、nope等校验库的支持,以及“自定义构建”(custom resolver)的能力。其接入方式统一通过useForm的resolver配置项:
import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; const schema = z.object({ firstName: z.string().min(1, 'First name is required'), age: z.coerce.number().min(18), }); function App() { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(schema), }); return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <input {...register('firstName')} /> {errors.firstName && <p>{errors.firstName.message}</p>} <input type="number" {...register('age')} /> {errors.age && <p>{errors.age.message}</p>} <input type="submit" /> </form> ); }resolver的底层协议定义在 src/types/resolvers.ts:一个 resolver 接收values、context与options(含criteriaMode、names、fields等),返回{ values, errors }或 Promise 形式的结果。schema 校验得到的错误消息会通过 schemaErrorLookup.ts 映射回各字段的errors对象,与内置规则产出的错误形态完全一致。仓库根目录的package.json的devDependencies中可见zod: ^3.25.76(package.json),可用于本地验证该流程;完整 schema 校验示例见 examples/V7/validationSchema.tsx 与 app/src/basicSchemaValidation.tsx。
六、验证触发模式(mode / reValidateMode)
原文档未显式展开验证模式,但这是快速入门后必知的核心配置。useForm支持通过mode配置首次验证触发时机,可选值定义在 src/constants.ts:
| mode | 行为 |
|---|---|
onSubmit(默认) | 仅在提交时验证 |
onBlur | 字段失焦时验证 |
onChange | 字段值变化时验证 |
onTouched | 字段首次交互(聚焦后失焦)后验证 |
all | blur 与 change 都验证 |
此外reValidateMode可配置错误后的重新验证时机,默认为onChange。
这些模式的行为在 e2e/basic.spec.ts 中有完整的 Playwright 端到端测试佐证:
- onSubmit 模式(e2e/basic.spec.ts):提交空表单后,全部必填字段错误同时出现,且首个错误字段
nestItem.nest1自动获得焦点; - onTouched 模式(e2e/basic.spec.ts):字段仅在“聚焦又失焦”后才显示错误,单纯输入内容不触发校验;
- onBlur 模式(e2e/basic.spec.ts):失焦即校验;
- onChange 模式(e2e/basic.spec.ts):输入过程中实时校验。
验证模式对性能的影响:选择onChange/all意味着每次输入都触发校验计算,重渲染更频繁;而默认的onSubmit将校验集中在提交时刻,配合 react-hook-form 的订阅式状态分发(表单控制层按需推送状态,见 src/logic/createFormControl.ts 与 shouldRenderFormState.ts),能最大限度减少不必要的组件重渲染。e2e 测试中通过#renderCount断言渲染次数也印证了这一特性(e2e/basic.spec.ts)。
七、源码视角:useForm 与验证流程
为了深入理解“为什么快”,可以顺着两条关键链路阅读源码:
- 表单控制层:
useForm在首次渲染时通过createFormControl创建控制实例(src/useForm.ts),实例内部维护字段注册表(_fields)、表单状态(_formState)与订阅者(_subjects)。useForm通过useIsomorphicLayoutEffect订阅状态变更,仅当被订阅的formState片段变化时才触发重渲染(src/useForm.ts)。 - 字段验证层:每次提交或触发校验时,validateField.ts 按顺序执行
min/max/minLength/maxLength/pattern/required/validate规则,appendErrors(src/logic/appendErrors.ts)负责在criteriaMode开启时收集全部失败规则;getValidateError(src/logic/getValidateError.ts)将自定义校验函数返回的字符串/布尔值规范化为错误对象。
仓库提供了全面的测试覆盖:单元测试位于 src/tests/logic(含 validateField.test.tsx、getValidationModes.test.ts 等),useForm行为测试位于 src/tests/useForm,端到端测试位于 e2e。阅读这些测试是深入理解各 API 行为最快的方式。
八、更多进阶资源
原文档顶部提供了“开始使用 / API / 示例 / 演示 / 表单构建器 / 常见问题”等导航入口(对应官方文档站),本仓库内可直接查阅的补充资料包括:
- 基础示例:app/src/basic.tsx、examples/V7/basic.tsx
- Schema 校验:app/src/basicSchemaValidation.tsx、app/src/customSchemaValidation.tsx
- 字段数组:app/src/useFieldArray.tsx、examples/V7/FieldArray.tsx
- 状态订阅:app/src/useFormState.tsx、app/src/useWatch.tsx
- Controller 集成:app/src/controller.tsx、examples/V7/customInput.tsx
- 表单重置:app/src/reset.tsx、examples/V7/resetForm.tsx
- API 类型报告:reports/api-extractor.api.md
本文所有代码示例均可直接复制运行(需配合 React 16.8+ 环境);更详细的 API 说明可进一步阅读 src/types 下的类型定义,或对照 src/tests中的测试用例理解各 API 的边界行为。
【免费下载链接】react-hook-form📋 React Hooks for form state management and validation (Web + React Native)项目地址: https://gitcode.com/gh_mirrors/re/react-hook-form
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考