验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

React Hook Form优雅处理表单使用的方法是什么

阅读:1035 来源:乙速云 作者:代码code

React Hook Form优雅处理表单使用的方法是什么

      受控组件与非受控组件

      受控组件

      先说说受控组件,以 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 的方式减少使用时的代码量,简单易上手,并且移除了不必要的重复渲染:

      React Hook Form优雅处理表单使用的方法是什么

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

      React Hook Form优雅处理表单使用的方法是什么

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

      React Hook Form优雅处理表单使用的方法是什么

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

      React Hook Form优雅处理表单使用的方法是什么

      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 函数创建一个表单对象,该函数返回一个包含 register 和 handleSubmit 等方法的对象。

      • 在表单中定义两个输入框,使用 register 函数注册表单输入组件,并指定组件的名称为 firstName 和 lastName。

      • 使用 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 元素,onChange 和 onBlur 是回调函数,用于处理该组件的值变化和失去焦点事件,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)}>
                <h3>Parent Form</h3>
                <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>
            <h3>Child Form</h3>
            <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 ,只需要将 useFormContext 与 FormProvider 搭配使用,就可以将一个嵌套表单的逻辑分离成多个组件进行处理,且可以在父级组件提交时统一获取并处理数据。

      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;

      分析一下上边这段代码:

      • 在这个示例中,我们使用了 useForm 和 useFieldArray 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 提供的 register、unregister 和 setValue 函数传递给了 useFieldArray,然后在 useFieldArray 内部维护一个数组 state,保存当前的数组值和对数组的操作。

      第三方组件

      当需要与第三方UI组件(如、