使用 React Hook Form 现代、高效的处理表单

核心心法:非受控组件 (Uncontrolled Components)
RHF 管理的表单,在数据更改时不会立刻渲染,而是类似于非受控组件那样特定时刻部分渲染,这带来了更高的性能。
但是同时,它又提供了一系列很实用的工具,能让你享受到受控组件一般的开发管理体验。
要理解 RHF,首先必须理解受控组件和非受控组件的区别。
-
受控组件 (Controlled Component):这是我们常规的 React 思维。表单元素的值由 React 的 state 来完全控制。用户的每一次输入(
onChange
)都会触发setState
,从而导致组件重新渲染。1
2const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />优点:数据流清晰,状态始终与 React state 同步,便于实时校验和动态控制。
缺点:频繁的 re-render 会带来性能开销,尤其是在大型复杂表单中。 -
非受控组件 (Uncontrolled Component):表单数据由 DOM 自身来处理和存储。 React 不直接控制输入框的值,而是在需要时通过
ref
去 DOM 中读取。1
2
3const inputRef = useRef(null);
<input ref={inputRef} />
// 在提交时通过 inputRef.current.value 读取值优点:性能极高,因为用户的输入不会触发 React 的重新渲染。
缺点:与 React 的声明式思想略有出入,数据流不是单向的,状态管理相对麻烦。
React Hook Form 的天才之处,就在于它以非受控组件为基础,通过 Hooks 的方式巧妙地封装了状态管理和校验逻辑,让你既能享受到非受控组件的性能优势,又能拥有媲美受控组件的开发体验和强大功能。
第一个上手实例:useForm
& register
现在,让我们通过一个最简单的例子,来看看 RHF 的魅力。
假设我们要创建一个包含用户名和邮箱的表单。
1 | import { useForm } from 'react-hook-form'; |
让我们来逐行剖析这段代码,理解其底层原理:
1. useForm()
这是 RHF 的大脑。调用它会返回一个对象,里面包含了管理整个表单所需的所有工具,大体如下:
1 | { |
我们这里解构了最常用的三个:register
, handleSubmit
, 和 formState
。
2. register("username", { ...validationRules })
这是连接 React 与 DOM 的桥梁。
当你使用扩展运算符 {...register("username")}
时,RHF 会默默地为你向 <input>
元素注入几个关键的 props,包括
name="username"
,这是第一个参数指定的,后续会作为提交的 json 的键。ref
,这是因为上文说的非受控组件原因。onChange
onBlur
等。
关键点:它会通过 ref
获取这个 DOM 节点的引用。当用户输入时,RHF 内部会监听 onChange
事件,但它不会立即触发 React 的 re-render。它只是在内部更新该字段的值。这就是性能优势的来源。
第二个参数是一个配置对象,用于定义校验规则。 RHF 内置了 required
, minLength
, maxLength
, pattern
等许多与原生 HTML 规范一致的规则。实际开发中,更可以结合 Zod 等第三方库作为其插件,请见:
3. handleSubmit(onSubmit)
这是一个高阶函数,扮演着“提交守门员”的角色。
-
当你点击提交按钮时,
handleSubmit
会首先执行。它会阻止表单的默认提交行为。 -
然后,它会触发内部的校验逻辑,收集所有通过
register
注册的字段的值。这个过程很像是 [[使用 form-serialize.js 一次拿到表单所有值 | form-serialize.js]] 做的事情。 -
校验:
-
如果校验通过,它会把你定义
onSubmit
函数作为回调来执行,并将收集到的、结构化的表单数据 ({ username: '...', email: '...' }
) 作为第一个参数传入。 -
如果校验失败,它不会调用
onSubmit
,而是会自动更新formState.errors
对象,此时由于errors
对象发生了变化,React 组件会进行一次渲染,从而将错误信息显示在界面上。
-
4.formState: { errors }
这是一个包含了表单当前状态的对象,里面还有更多信息,比如 isDirty
(是否被修改过), isValid
(是否有效), 当然还有我们用到的 errors
(错误信息)。
1 | formState: { |
但是通常情况,我们只用到了 errors 。
这个对象是响应式的,当它的状态改变时,会触发组件更新。因为这个原因,我们可以使用这个解构出的 errors,来在 UI 上呈现。
解构出的其他工具
上面只说了四个常用的,我会在这里补充其他我用过的工具的注意点。
setValue(name, value, options?)
setValue 能单点更新某个值,而不会默认重置整个表单的提交/脏/计数等全局状态,这一点就和下面要说的 reset 不同了。
-
用途:程序化地设置单个字段的值(例如:根据另一个字段计算结果、外部数据更改、第三方控件回调等)。
-
常用选项(第三参):
{ shouldValidate?: boolean, shouldDirty?: boolean, shouldTouch?: boolean }
-
shouldValidate
为 true 会触发字段验证; -
shouldDirty
控制是否将字段标记为 dirty; -
shouldTouch
控制是否将字段标记为 touched。
-
-
注意:通常
setValue
只影响指定字段,。适合“单点更新”。示例:
1 | // 计算 total 并更新(同时触发验证) |
reset(values?, options?)
-
用途:重置整个表单的值与状态(默认会把
values
替换为传入值或defaultValues
,并且清除/重置 formState,如isDirty
、isSubmitSuccessful
、submitCount
等)。通常用于:提交成功后清空表单、把远端载入的数据填入表单(把那套数据当作新“初始值”)等。 -
选项:
reset
有一组keep*
选项,可以选择保留 某些状态(例如keepValues
、keepErrors
、keepDirty
、keepTouched
、keepIsValid
、keepSubmitCount
等),以便细粒度控制(比如想保留错误但重置值,或保留值但重置 dirty 状态等)。示例:
1 | // 提交后,重置为空表单(清除所有状态) |
有一个常见的需求,是在用户提交表单后,清空之前填入的内容。如果提交失败,则不清空。
理所当然的,我们会想着直接用 reset() 而不传参,但是在部分情况下,这有时会导致 bug。
如果你之前在某个地方,已经用过 reset 但是传参,那么此时程序把它当成 default,而不再使用先前设定的 defauleValue 了。
为了解决这个 bug,建议固定使用 reset(defaultValue) 来在成功的时候清空表单,defaultValue 就是你手动设置的那个表单初始值,大部分情况下为空。
例如,在定义的时候:
1 | const { |
那么下面你想清空表单的时候,使用
1 | reset({ |
resetField(name, options?)
如果你只想重置单个字段(恢复到 defaultValues
或清空该字段的状态),可以用 resetField("x")
,它是对单字段的重置工具(区别于 reset()
的全表重置)
- 标题: 使用 React Hook Form 现代、高效的处理表单
- 作者: 三葉Leaves
- 创建于 : 2025-09-05 00:00:00
- 更新于 : 2025-09-05 12:58:10
- 链接: https://blog.oksanye.com/77abdd80c633/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。