人人都会AI编程

受控与非受控结合、注册机制、表单验证

更新时间:2026-07-10

React Hook Form(RHF)之所以在性能上大幅领先纯受控表单方案,关键在于它采用了非受控(Uncontrolled)为主、受控(Controlled)为辅的混合模式。理解其注册机制和验证流程,才能真正驾驭这个库。

非受控为主的性能优势

传统受控组件中,每次输入都触发 setState,导致组件重新渲染:

// 纯受控写法:每次按键都会 render
function Form() {
  const [name, setName] = useState('');
  return <input value={name} onChange={e => setName(e.target.value)} />;
}

React Hook Form 默认通过 ref 直接读取 DOM 的真实值,不触发 React 的 setState,因此不会导致整个表单组件重新渲染:

function Form() {
  const { register } = useForm();
  // register 内部使用 ref 注册,输入时不触发重新渲染
  return <input {...register('name')} />;
}

只有在特定时机(如表单提交或校验失败时),RHF 才会触发必要的局部渲染,极大减少了渲染开销。这对于大型表单(几十甚至上百个字段)的性能提升非常明显。

受控与非受控的混合使用

RHF 并不排斥受控组件。如果你需要监听某个输入的变化来做实时校验或联动,可以通过 watch 或直接使用受控组件:

1. 使用 watch 监听某个字段(非受控方式获取实时值)

const { register, watch } = useForm();
const selectedCountry = watch('country'); // 实时获取当前值,但仅在值变化时触发渲染

return (
  <>
    <select {...register('country')}>
      <option value="cn">中国</option>
      <option value="us">美国</option>
    </select>
    {selectedCountry === 'cn' && <input {...register('province')} placeholder="省份" />}
  </>
);

2. 与受控组件(如第三方 UI 库)集成

当使用 Ant Design、Material UI 等受控组件时,需要使用 ControlleruseController 包裹:

import { useForm, Controller } from 'react-hook-form';
import { Select, Input } from 'antd';

function Form() {
  const { control } = useForm();

  return (
    <Controller
      name="category"
      control={control}
      render={({ field }) => (
        <Select {...field}>
          <Select.Option value="a">A</Select.Option>
          <Select.Option value="b">B</Select.Option>
        </Select>
      )}
    />
  );
}

此时 Controller 内部仍然使用 ref 跟踪值,但通过 render 将值传回给受控组件,实现非受控的性能和受控的灵活性。

注册机制:register 到底做了什么

register 是 RHF 的核心 API,它的作用是将字段的信息(名称、校验规则等)注册到表单实例内部,并返回 ref 等属性,让 React Hook Form 能够直接管理表单元素的值和校验状态

const { register } = useForm();

// register 返回 { onChange, onBlur, ref, name } 四个属性
<input {...register('email', { required: '邮箱必填', pattern: /^\S+@\S+$/ })} />

当你在 input 上展开 register 返回的对象时:

  • ref 绑定到真实 DOM 元素,RHF 通过 ref 直接读取输入值,避免 React state 更新。
  • name 作为字段唯一标识,存储在内部状态树中。
  • onChangeonBlur 用于捕获变化事件,更新内部存储的值(内存中的数据对象),并管理触控(touched)和脏检(dirty)状态。

注册时机:当组件挂载时,register 将字段信息存入表单实例的内部管理对象;组件卸载时,RHF 会自动取消注册,避免内存泄漏或脏数据残留。

表单验证:声明式规则与自定义校验

RHF 支持丰富的校验规则,可以内联在 register 中,也可以借助第三方校验库(如 Zod、Yup)进行结构式校验。

声明式规则(内置)

<input
  {...register('age', {
    required: '请输入年龄',
    min: { value: 18, message: '必须年满18岁' },
    max: { value: 60, message: '年龄不能超过60岁' },
    validate: value => value % 1 === 0 || '必须为整数',
  })}
/>
{errors.age && <span>{errors.age.message}</span>}

requiredminmaxmaxLengthpattern 等均为内置规则,同时还支持 validate 自定义同步校验函数。可以定义多个校验规则,按顺序执行,第一个错误被捕获。

异步校验

register('username', {
  validate: async (value) => {
    const isAvailable = await checkUsernameAvailable(value);
    return isAvailable || '用户名已被占用';
  }
});

异步校验在字段值发生变化时会自动发起请求,并根据 Promise 返回结果决定是否通过。RHF 内部会管理请求竞态,确保只有最新一次请求的结果被采纳。

集成 Zod(或 Yup)实现 schema 校验

import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('邮箱格式错误'),
  password: z.string().min(6, '密码至少6位'),
});

function Form() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema)
  });

  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <p>{errors.email.message}</p>}
      <input type="password" {...register('password')} />
      {errors.password && <p>{errors.password.message}</p>}
      <button type="submit">提交</button>
    </form>
  );
}

使用 resolver 后,所有校验规则统一由 schema 定义,register 中无需再重复写校验规则,实现验证与 UI 的分离,便于复用和维护。

触控模式与错误展示策略

RHF 提供多种模式控制何时触发验证和错误显示:

  • modeonSubmit(默认)、onBluronChangeonTouchedall
  • reValidateMode:控制重新验证的模式(通常保持默认 onChange
useForm({ mode: 'onBlur' });

设为 onBlur 时,用户离开输入框才触发校验,体验更友好。结合 errors 对象中的 typesmessage,可以精确控制错误信息的显示。

综合示例:注册 + 校验 + 联动

function RegistrationForm() {
  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm({
    mode: 'onBlur',
    resolver: zodResolver(schema),
  });

  const password = watch('password');

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('email', { required: '邮箱必填' })}
        placeholder="邮箱"
      />
      {errors.email && <span>{errors.email.message}</span>}

      <input
        type="password"
        {...register('password', {
          required: '密码必填',
          minLength: { value: 8, message: '至少8位' },
        })}
        placeholder="密码"
      />
      {errors.password && <span>{errors.password.message}</span>}

      <input
        type="password"
        {...register('confirmPassword', {
          required: '请确认密码',
          validate: value =>
            value === password || '两次密码不一致',
        })}
        placeholder="确认密码"
      />
      {errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}

      <button type="submit">注册</button>
    </form>
  );
}

这个例子综合了受控与非受控理念:字段由 ref 管理渲染高效,watch 获取密码做联动校验,声明式规则和自定义 validate 同时使用,实现了高性能且交互完善的形式。

React Hook Form 的混合模式巧妙地解决了传统受控表单的性能痛点,而其注册机制和验证体系则提供了强大的可组合性和灵活性。掌握这些,就能轻松应对从简单登录到复杂多步骤表单的各种场景。