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

这篇“React Hook Form优雅处理表单使用的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“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 第1张

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

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

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

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

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

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

    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)}>
              <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 ,只需要将 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) =&gt; console.log(data);
      return (
        &lt;form onSubmit={handleSubmit(onSubmit)}&gt;
          &lt;Controller
            name="firstName"
            control={control}
            defaultValue=""
            rules={{ required: true }}
            render={({ field }) =&gt; (
              &lt;TextField label="First Name" {...field} /&gt;
            )}
          /&gt;
          &lt;Controller
            name="lastName"
            control={control}
            defaultValue=""
            rules={{ required: true }}
            render={({ field }) =&gt; (
              &lt;TextField label="Last Name" {...field} /&gt;
            )}
          /&gt;
          &lt;Button type="submit" variant="contained" color="primary"&gt;
            Submit
          &lt;/Button&gt;
        &lt;/form&gt;
      );
    }
    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优雅处理表单使用的方法是什么”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注蜗牛博客行业资讯频道。

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    评论

    有免费节点资源,我们会通知你!加入纸飞机订阅群

    ×
    天气预报查看日历分享网页手机扫码留言评论电报频道链接