React Hook Form 优雅处理表单使用指南

目录
  • 受控组件与非受控组件
    • 受控组件
    • 非受控组件
    • 为什么需要非受控组件
  • React Hook Form 是什么?
  • React Hook Form 的使用姿势
    • 数据收集
    • 表单嵌套
    • 条件判断
    • 表单列表
    • 第三方组件
    • Typescript 支持
  • 总结

受控组件与非受控组件

受控组件

先说说受控组件,以 input 为例:

const [value,setValue] = useState('')
<input value={value} onChange={(e)=> setValue(e.target.value)} />

在上面的代码中,我们通过通过自己维护一个 state 来获取或更新 input 输入的值,以这种方式控制取值的 表单输入元素 就叫做 受控组件

非受控组件

那么什么是非受控组件呢?在 React 中,非受控组件是指表单元素的值由 DOM 节点直接处理,而不是由 React 组件来管理。如下例:

import React, { useRef } from 'react';
function UncontrolledForm() {
  const nameInputRef = useRef(null);
  const emailInputRef = useRef(null);
  const passwordInputRef = useRef(null);
  function handleSubmit(event) {
    console.log('Name:', nameInputRef.current.value);
    console.log('Email:', emailInputRef.current.value);
    console.log('Password:', passwordInputRef.current.value);
    event.preventDefault();
  }
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={nameInputRef} />
      </label>
      <label>
        Email:
        <input type="email" ref={emailInputRef} />
      </label>
      <label>
        Password:
        <input type="password" ref={passwordInputRef} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

在这个例子中,我们使用 useRef Hook 创建了一个 ref 对象,并将其赋值给每个 input 元素的 ref 属性。在 handleSubmit 函数中,我们使用 ref.current.value 来获取每个 input 元素的值。这里的每个input 元素都是非受控组件,因为它们的值由 DOM 节点直接处理,而不是由 React 组件来管理。

当然,这意味着当用户输入数据时,React 无法追踪表单元素的值。因此,当您需要访问表单元素的值时,您需要使用DOM API来获取它们。

为什么需要非受控组件

在 React 中,通常使用受控组件来处理表单。受控组件表单元素的值由 React 组件来管理,当表单数据发生变化时,React 会自动更新组件状态,并重新渲染组件。这种方式可以使得表单处理更加可靠和方便,也可以使得表单数据和应用状态之间保持一致。

但在实际的开发中,表单往往是最复杂的场景,有的表单有数十个字段,如果使用受控组件去构建表单,那么我们就需要维护大量 state,且 React 又不像 Vue 可以通过双向绑定直接修改 state 的值,每一个表单字段还需要定义一下 onChange 方法。因此在维护复杂表单时,使用受控组件会有很大的额外代码量。

为了解决受控组件带来的问题,我们可以使用非受控组件来构建表单。受控组件主要有以下三个优点

  • 可以减少组件的 代码量和复杂度,因为非受控组件不需要在组件状态中保存表单数据。
  • 可以更好地 处理大量表单数据,因为非受控组件可以让您直接操作DOM元素,而不需要将所有表单数据存储在组件状态中。
  • 可以更容易地与第三方 JavaScript 库和表单处理代码集成,因为非受控组件使您能够使用 DOM API 或 ref 直接访问表单元素,而不是在 React 中重新实现所有的表单处理逻辑。

React Hook Form 是什么?

React Hook Form 是一个基于 React 的 轻量级表单验证库。它使用了 React Hook API,让表单验证变得简单、易用、高效。React Hook Form 的主要目标是提供一个简单易用的表单验证解决方案,同时还能保持高性能和低开销。

React Hook Form 的特点都在官网首页 react-hook-form.com 中以可交互的形式展示,包括了以下几点:

  • 通过 React Hook 的方式减少使用时的代码量,简单易上手,并且移除了不必要的重复渲染:

  • 隔离重复渲染,自组件重新渲染时不会触发父组件或兄弟组件的重新渲染:

  • 订阅机制,与上一点相似,能够订阅单个输入和表单状态更新,而无需重新呈现整个表单:

  • 组件渲染速度足够快,而且代码库非常小,压缩后只有几 KB 大小,不会对页面性能造成任何影响:

React Hook Form 的使用姿势

数据收集

先看看最基础的表单实现:

import React from "react";
import { useForm } from "react-hook-form";
function MyForm() {
  const { register, handleSubmit } = useForm();
  const onSubmit = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("firstName")} />
      <input {...register("lastName")} />
      <button type="submit">Submit</button>
    </form>
  );
}

咱们来分析一下这段代码:

  • 使用 useForm 函数创建一个表单对象,该函数返回一个包含 registerhandleSubmit 等方法的对象。
  • 在表单中定义两个输入框,使用 register 函数注册表单输入组件,并指定组件的名称为 firstNamelastName
  • 使用 React Hook Form 提供的 handleSubmit 函数来管理表单的提交和数据验证。

这里我们不需要定义任何 state 即可在 submit 时获取到表单中的数据,接下来我们补充一下基本的表单验证和错误提示:

import React from "react";
import { useForm } from "react-hook-form";
function MyForm() {
    const onSubmit = (data) => {
      console.log(data);
    };
    const { register, handleSubmit, formState: { errors } } = useForm();
    return (
      <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register("firstName", { required: true })} />
        {errors.firstName && <p>First name is required.</p>}
        <input {...register("lastName", { required: true })} />
        {errors.lastName && <p>Last name is required.</p>}
        <button type="submit">Submit</button>
      </form>
    );
}

咱们再分析一下这段代码:

  • register 函数中可以指定表单输入组件的 验证规则 ,例如使用 required 规则来验证输入框的必填项。
  • 在表单提交处理函数中,可以调用 React Hook Form 提供的 handleSubmit 函数来自动执行表单验证,并返回验证结果。只有表单验证通过才会执行 onSubmit 方法。
  • 如果表单验证失败,可以使用 errors 对象来获取每个表单输入组件的验证错误信息,并在 UI 上显示错误提示。

register 函数是用来注册表单输入组件的,当组件注册之后,React Hook Form 会自动收集该组件的值,并根据验证规则进行验证。 register 函数会返回一个对象,其中包含了一些属性和方法,例如:

const { ref, onChange, onBlur, name } = register("firstName");

ref 属性是一个引用,指向该输入组件的 DOM 元素,onChangeonBlur 是回调函数,用于处理该组件的值变化和失去焦点事件,name 是该组件的名称。

register 函数内部会创建一个管理表单输入组件的对象,包含了该组件的名称、引用、验证规则等信息。同时,还会将该对象保存在 React Hook Form 内部的一个数据结构中。

在表单提交时,React Hook Form 会遍历管理的所有表单输入组件,并收集它们的值,并根据其注册时定义的验证规则进行验证。

到这里,一个最基本的表单验证及提交就已经实现了。当然,在实际开发中,表单之所以复杂是由于各种条件渲染及表单嵌套引起的,那么我们接下来再看看使用 React Hook Form 如何处理这些场景。

表单嵌套

还是一样,先看看示例:

父级表单

// ParentForm.jsx
import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import ChildForm from "./ChildForm";
function ParentForm() {
  const methods = useForm();
  const { register, handleSubmit, formState: { errors } } = methods;
  const onSubmit = (data) => {
    console.log(data);
  };
  return (
      <FormProvider {...methods}>
        <form onSubmit={handleSubmit(onSubmit)}>
          <h2>Parent Form</h2>
          <label htmlFor="firstName">First Name</label>
          <input {...register("firstName", { required: true })} />
          {errors.firstName && <p>First name is required.</p>}
          <label htmlFor="lastName">Last Name</label>
          <input {...register("lastName", { required: true })} />
          {errors.lastName && <p>Last name is required.</p>}
          <ChildForm/>
          <button type="submit">Submit</button>
        </form>
    </FormProvider>
  );
}
export default ParentForm;

子级表单

import React from "react";
import { useFormContext } from "react-hook-form";
function ChildForm() {
  const { register, errors } = useFormContext();
  return (
    <div>
      <h2>Child Form</h2>
      <label htmlFor="childFirstName">Child First Name</label>
      <input {...register("child.firstName", { required: true })} />
      {errors.child?.firstName && <p>Child first name is required.</p>}
      <label htmlFor="childLastName">Child Last Name</label>
      <input {...register("child.lastName", { required: true })} />
      {errors.child?.lastName && <p>Child last name is required.</p>}
    </div>
  );
}
export default ChildForm;

分析一下这两个组件的代码:

ParentForm 组件中,我们使用 useForm hook 来获取表单的注册函数、表单状态等信息,并使用 FormProvider 组件将其传递给所有的子组件。

ChildForm 组件中,我们使用了 useFormContext hook 来获取父表单的注册函数和表单状态。

这里的两个表单组件间并不需要咱们去单独定义 props ,只需要将 useFormContextFormProvider 搭配使用,就可以将一个嵌套表单的逻辑分离成多个组件进行处理,且可以在父级组件提交时统一获取并处理数据。

FormProvider 是 React Hook Form 提供的一个组件,用于在 React 组件树中向下传递 useForm hook 的实例。它创建了一个 React Context,并将 useForm hook 的实例作为 Context 的值,然后通过 Context.Provider 组件将这个值传递给所有子组件.

useFormContext 则可以在子组件中获取到 FormProvider 提供的 useForm hook 的返回值。在使用 useFormContext 时,不需要手动使用 Context.Provider 将值传递给子组件,而是可以直接从 useFormContext 中获取,简化嵌套表单的代码逻辑。

条件判断

import React from "react";
import { useForm } from "react-hook-form";
function ExampleForm() {
  const { register, handleSubmit, watch } = useForm();
  const onSubmit = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="hasAge">Do you have an age?</label>
      <select {...register("hasAge")}>
        <option value="yes">Yes</option>
        <option value="no">No</option>
      </select>
      {watch("hasAge") === "yes" && (
        <>
          <label htmlFor="age">Age</label>
          <input {...register("age", { required: true, min: 18 })} />
          {watch("age") && <p>You must be at least 18 years old.</p>}
        </>
      )}
      <button type="submit">Submit</button>
    </form>
  );
}
export default ExampleForm;

我们在 hasAge 输入框上使用了一个简单的条件渲染:只有当用户选择了 "Yes" 时,才会渲染 age 输入框。然后使用 watch 函数来监听输入框的值,并在输入的值小于 18 时显示相应的错误信息。

watch 函数用来监听指定的输入并返回它们的值。在渲染输入值和进行条件渲染时经常用到。

表单列表

import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
function ListForm() {
  const { register, control, handleSubmit } = useForm({
    defaultValues: {
      list: [{ name: "" }, { name: "" }, { name: "" }]
    }
  });
  const { fields, append, remove } = useFieldArray({
    control,
    name: "list"
  });
  const onSubmit = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`list.${index}.name`, {
              required: "This field is required"
            })}
            defaultValue={field.name}
          />
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}
      <button type="button" onClick={() => append({ name: "" })}>
        Add Item
      </button>
      <button type="submit">Submit</button>
    </form>
  );
}
export default ListForm;

分析一下上边这段代码:

  • 在这个示例中,我们使用了 useFormuseFieldArray hook 来处理一个表单列表。其中 list 属性是一个包含 3 个空对象的数组。
  • 使用 fields.map 方法遍历 fields 数组,渲染出每一个列表项。
  • 使用 remove 方法为每个列表项添加了一个 "Remove" 按钮,使得用户可以删除不需要的列表项。我们还使用 append 方法添加了一个 "Add Item" 按钮,可以添加新的列表项。

这段代码的核心就是 useFieldArray,它专门用于处理表单列表的场景,使用时我们将 useForm 返回的 control 传入 useFieldArray hook 中,并为这个列表定义一个名字,hook 会为我们返回一些操作列表的方法,在遍历渲染列表时,我们将每一个子项单独进行注册就可以实现表单列表的动态数据更改了。

需要注意的是,当使用 useFieldArray 处理表单中的数组字段时,每个字段都必须有一个 唯一的 key 值,这样才能正确地进行数组的添加、删除、更新等操作。如果数组中的字段没有 key 值,useFieldArray 会自动为每个字段生成一个随机的 key 值。

在内部实现上,useFieldArray 使用了 useFormContext 将 FormProvider 提供的 registerunregistersetValue 函数传递给了 useFieldArray,然后在 useFieldArray 内部维护一个数组 state,保存当前的数组值和对数组的操作。

第三方组件

当需要与第三方UI组件(如<DatePicker /><Select /><Slider />等)集成时,如果使用register 注册这些第三方UI组件,可能会遇到如无法正确更新表单数据、错误处理、性能差等问题。

因此,使用Controller 是一种更好的解决方案,可以将表单数据与 React Hook Form 状态管理集成在一起,并使用render 函数来直接渲染第三方UI组件。下面放个例子:

import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Button } from "@material-ui/core";
function ControllerForm() {
  const { control, handleSubmit } = useForm();
  const onSubmit = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="firstName"
        control={control}
        defaultValue=""
        rules={{ required: true }}
        render={({ field }) => (
          <TextField label="First Name" {...field} />
        )}
      />
      <Controller
        name="lastName"
        control={control}
        defaultValue=""
        rules={{ required: true }}
        render={({ field }) => (
          <TextField label="Last Name" {...field} />
        )}
      />
      <Button type="submit" variant="contained" color="primary">
        Submit
      </Button>
    </form>
  );
}
export default ControllerForm;

control 是一个对象,它提供了一些方法和属性,通过使用 control,我们可以将 React Hook Form 中的数据与实际渲染的表单组件进行绑定,从而让 React Hook Form 管理表单中所有的输入和校验逻辑。

field<Controller> 组件通过 render 回调函数传递给其子组件的一个对象,field 对象中包含了一些属性,如 valueonChangeonBlur 等,这些属性传递给子组件,用于设置和更新表单控件的值,以及处理表单控件的事件,如用户输入、聚焦、失焦等。

Controller的好处是可以将表单数据和表单状态统一管理,同时避免了对表单数据的手动处理。此外,它还可以优化表单的渲染性能,并提供更好的错误处理机制,因为它可以自动处理错误消息和验证规则。

Typescript 支持

React Hook Form 提供了完整的 TypeScript 支持:

import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";
type FormValues = {
  firstName: string;
  lastName: string;
  age: number;
};
function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("firstName", { required: true })} />
      {errors.firstName && <span>This field is required</span>}
      <input {...register("lastName", { required: true })} />
      {errors.lastName && <span>This field is required</span>}
      <input {...register("age", { required: true, min: 18 })} />
      {errors.age && (
        <span>
          {errors.age.type === "required"
            ? "This field is required"
            : "You must be at least 18 years old"}
        </span>
      )}
      <button type="submit">Submit</button>
    </form>
  );
}

我们使用 FormValues 类型定义表单数据类型,并在 useForm 钩子中使用 FormValues 泛型接口。这使得我们可以在注册表单控件时提供正确的类型定义,并在 handleSubmit 函数中提供正确的表单数据类型。还可以使用泛型来定义错误消息的类型,可以用于准确地描述表单控件的错误状态,并提供适当的错误消息。提高代码的可读性和可维护性。

总结

除了上述的一些表单使用姿势,在官方的 react-hook-form.com/advanced-us… 页面还可以看到一些高级特性的示例,例如 Schema 解析为表单实现表单组件测试表单与 API 间的数据转换 等等。

总的来说,React Hook Form 提供了一种简单、灵活的方式来管理表单状态,支持非受控组件方式以及各种复杂的表单场景,同时也提供了 Typescript 支持,是一个值得尝试的表单管理库。

以上就是React Hook Form 优雅处理表单使用指南的详细内容,更多关于React Hook Form处理表单的资料请关注我们其它相关文章!

(0)

相关推荐

  • react ant Design手动设置表单的值操作

    1.设置表单的值 this.props.form.setFieldsValue({ name:"张三", }); 2.清空表单的值 this.props.form.resetFields(); 3.获取某一输入框的值 this.props.form.getFieldValue('newPassword'); 4.获取整个表单的值 this.props.form.getFieldsValue(); 多看官方文档就知道这些东西了 补充知识:react使用antd表单赋值,用于修改弹框 1.

  • react antd实现动态增减表单

    之前写动态表单遇到过坑,就是用index下标做key会导致bug,而且很严重! 今天有空写下文章记录下:怎么处理和逻辑 我用的是antd3的版本,3和4的表单有点不一样,不过差别应该不大. 需求: 1.选择类型切换展示固定的模板 2.通过新增字段可以动态增减表单里面的每一行 3.控制每一行的字段是否需要必填 4.编辑时候回填参数 效果图: 部分关键代码: import React, { Component } from 'react'; import styles from './index.l

  • JS判断form内所有表单是否为空的简单实例

    如下所示: function checkForm(){ var input_cart=document.getElementsByTagName_r("INPUT"); for(var i=0; i<input_cart.length; i++) { if(input_cart[i].value==""||input_cart[i].value==null) { alert("信息不能为空!"); input_cart[i].focus()

  • 浅谈layui 绑定form submit提交表单的注意事项

    如下所示: <form method="post" class="layui-form"> <input type="text" name="name" placeholder="用户名" required lay-verify="required" class="layui-input layui-form-danger login-input"

  • ajax XMLHTTP Post Form时的表单乱码综合解决

    Part I Post中文内容  先看看E文的表单是怎么提交的: 复制代码 代码如下: <SCRIPT language="JavaScript">   strA = "submit1=Submit&text1=scsdfsd";   var oReq = new ActiveXObject("MSXML2.XMLHTTP");   oReq.open("POST","http://ServerN

  • Jquery.Form 异步提交表单的简单实例

    http://www.vaikan.com/docs/jquery.form.plugin/jquery.form.plugin.html# 1. 在你的页面里写一个表单.一个普通的表单,不需要任何特殊的标记: 复制代码 代码如下: <form id="myForm" method="post" action="/Home/AjaxForm"><div>Name:<input id="username&qu

  • jquery动态改变form属性提交表单

    有些情况下,同一个form在不同的情况下提交到不同的处理动作,可以在js中动态改变form的属性,满足不同条件的form提交需求. 如: 复制代码 代码如下: <form id="form" name="form" method="POST" enctype="multipart/form-data" action="action1.jsp" target="iframe">

  • javascript获取form里的表单元素的示例代码

    //获取form对象 var form=document.getElementById('my_form'); //用户名input对象 user_name是对象的name属性 var userName=form.user_name; //用户名清空 userName.value=''; //用户密码input对象 password是对象的name属性 var password=form.password; //用户密码清空 password.value=";

  • React如何利用Antd的Form组件实现表单功能详解

    一.构造组件 1.表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等. 这里先引用了封装的表单域 <Form.Item /> 2.使用Form.create处理后的表单具有自动收集数据并校验的功能,但如果不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用Form.create并自行处理数据 经过Form.create()包装过的组件会自带this.props.form属性,this.props.form提供了很多API来处理数据,如getFieldDe

  • react使用antd表单赋值,用于修改弹框的操作

    1.使用getFieldDecorator的initialValue 2.在state里定义一个变量存表格的数据 3.给打开弹框的方法传个record 4.把表格里的值存到state 5.把在state里存的值传给弹框 6.获取传过来的值 7.在取消方法和修改成功后中给赋空值,要不然,点击添加的方法表单里面会有值 7.OK 补充知识:react中使用antd的表单重置数据 resetFields 重置一组输入控件的值(为 initialValue)与状态,如不传入参数,则重置所有组件 Funct

随机推荐