Skip to content

react-hook-form

react-hook-form是一个用于react表单校验的库。很多UI框架(如antd、element-ui等)中的表单组件已经内置了它的部分功能,包括数据收集、字段校验、数值转化等。不同于这些带UI的表单组件,react-hook-form完全脱离UI,仅关注数据流间的运算逻辑,这意味着你可以在任何react场景使用它,在其它UI框架甚至根本不使用UI框架的场景下这尤为重要。

useForm

useForm是一个用于管理表单的自定义hook,它接收一个配置对象作为入参。

基本用法如下:

tsx
import { useForm } from "react-hook-form";

interface FormData {
  email: string;
  password: string;
}

export function Form() {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<FormData>({
    defaultValues: {
      email: "",
      password: "",
    },
    reValidateMode: "onChange",
  });

  const onSubmit = (data: FormData) => {
    console.log(data);
  };
  const onReset = () => reset();

  return (
    <form action="#" onSubmit={handleSubmit(onSubmit)} onReset={onReset}>
      <input type="email" {...register("email")} />
      {errors.email && <p>{errors.email.message}</p>}
      <input type="password" {...register("password")} />
      {errors.password && <p>{errors.password.message}</p>}
      <input type="submit" value="submit" />
      <input type="reset" value="reset" />
    </form>
  );
}

register

useForm返回的register用于在对应的表单中注册字段(支持校验、嵌套字段、数组字段)。

它返回一个包含以下四个属性的对象:

NameTypeDesc
onChangefunctioninput的onChange handler
onBlurfunctioninput的onBlur handler
refReact.Ref收集input实例,以支持error时的focus
namestringinput的name属性

register通过它返回的对象接管了对input身上props(仅以上四个)的操作,这使得在大部分input(除radio、submit、image、reset、button外)、select和textarea中,它都能像上例中情况一样直接使用。

自定义组件(尤其是props与register返回的对象不能对应的),则往往不适用。

NOTE: registery通过非受控组件的方式捕获input。它能在一定程序上自动识别inputtype以自动选择关注input的那个属性作为fieldvalue。例如:当typecheckbox时,会关注checked而非value,当typefile时,会关注file而非value

useController

在不支持直接使用register的情况下,改为使用useController接管组件。

它返回一个包含以下三个属性的对象:

NameTypeDesc
field.onChangefunction用来手动触发change event
field.onBlurfunction用来手动触发blur event
field.valueT用来手动指定value的使用场景
field.namestring用来手动指定input的name属性
field.refReact.Ref用来手动指定元素的ref
fieldState.invalidboolean当前字段是否无效
fieldState.isTouchedboolean当前input是否被touch
fieldsState.isDirtyboolean当前字段状态是否为脏数据
fieldState.errorerror当前字段校验错误时的信息
formState.isDirtyboolean表单状态是否为脏数据
formState.dirtyFieldsobject表单中为脏的字段
formState.touchedFieldsobject表单中处于touch的字段
formState.defaultValuesobject表单数据的默认值(初始表单数据)
formState.isSubmittedboolean表单是否已提交
formState.isSubmitSuccessfulboolean表单是否已成功提交(提交后运行环境未抛出异常)
formState.isSubmittingboolean表单是否提交中
formState.isLoadingboolean表单是否正加载初始表单数据
formState.submitCountnumber表单被提交的次数
formState.isValidboolean表单字段是否没有错误
formState.isValidatingboolean表单字段是否正在进行校验
formState.errorsboolean表单字段的校验错误信息

它的使用方式与register稍有不同。

tsx
import React from "react";
import { useController, useForm, Control } from "react-hook-form";

interface FieldsValue {
  email: string;
  password: string;
  checkbox: boolean;
}

interface CheckboxProps {
  control: Control<FieldsValue>;
}

type HandleChange = React.ChangeEventHandler<HTMLInputElement>;

export function Checkbox(props: CheckboxProps) {
  const { control } = props;

  const {
    field,
    fieldState: { error },
  } = useController({
    control,
    name: "checkbox",
    rules: { required: { value: true, message: "checkbox required" } },
  });

  const handleChange: HandleChange = (evt) => {
    const value = evt.target.checked;
    field.onChange(value);
  };

  return (
    <div>
      <input
        type="checkbox"
        ref={field.ref}
        checked={field.value}
        onChange={handleChange}
        onBlur={field.onBlur}
        name={field.name}
      />
      {error && <p>{error.message}</p>}
    </div>
  );
}

NOTE:register不同,field通过受控组件的方式来捕获input。这意味着,在typecheckbox的情况下,它会收集inputchecked会为value。注意:无论是register还是field,都不支持typeradio的情况。

Controller

Controller组件是useController的组件式API,其作用是useController基本一致。

但使用方法稍有不同:

tsx
import { Controller, Control } from "react-hook-form";

interface FieldsValue {
  email: string;
  password: string;
  checkbox: boolean;
  tel: string;
}

interface ItemEmailProps {
  control: Control<FieldsValue>;
}

export function ItemTell(props: ItemEmailProps) {
  const { control } = props;
  return (
    <Controller
      name="tel"
      control={control}
      defaultValue=""
      rules={{ required: "tel is required" }}
      render={({ field, fieldState: { error } }) => {
        return (
          <div>
            <input
              type="tel"
              value={field.value}
              onChange={field.onChange}
              onBlur={field.onBlur}
              ref={field.ref}
              name={field.name}
            />
            {error && <p>{error.message}</p>}
          </div>
        );
      }}
    ></Controller>
  );
}

Yup

除内置的校验规则以外,react-hook-form还支持接入Yup这样的库进行校验。

以Yup为例,开始前需安装@hookform/resolvers和yup。

tsx
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useForm } from "react-hook-form";

const schema = yup.object({
  date: yup.string().required().min(8).max(18),
});

export function FormAddress() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(schema),

    // Not recommoned
    async defaultValues() {
      return {
        date: "2022-01-01",
      };
    },
  });

  return (
    <form action="#" onSubmit={handleSubmit((d) => console.log(d))}>
      <div>
        <input type="date" {...register("date")} />
        {/* @ts-ignore */}
        {errors.date && <p>{errors.date?.message}</p>}
      </div>
      <input type="submit" value="submit" />
    </form>
  );
}

NOTE: 不能使用yup指定字段的默认值。

Coded by Yang_Lee