前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈表单受控性及结合Hooks应用

浅谈表单受控性及结合Hooks应用

作者头像
政采云前端团队
发布2023-12-21 14:10:14
3170
发布2023-12-21 14:10:14
举报
文章被收录于专栏:采云轩

1 前言

form 几乎是 web 开发中最常用的元素之一,而作为前端接口仔和表单的关系可以说紧密而不可分割。在本文中将介绍在 React 中受控和非受控表单是如何使用的,以及现代化使用 hooks 来管理 form 状态。

2 受控和非受控表单差异

2.1 受控表单的特点和使用场景

受控表单是指表单元素的值受 React 组件的 state 或 props 控制。特点:

表单元素的值保存在组件的 state 中,以便在需要时进行访问、验证或提交。每当用户输入发生变化时,需要手动更新 state 来反映新的值。可以通过 state 的值来进行表单元素的验证,并提供实时的错误提示。

使用场景:

  • 需要对用户输入进行验证和处理的表单
  • 需要实时反映用户输入的值的表单
  • 需要根据表单元素的值动态地改变其他组件的状态或行为等情况时会使用到受控表单 示例代码:
代码语言:javascript
复制
import React, { useState } from 'react';

function ControlledForm() {
  const [phone, setPhone] = useState('');
  const handlePhoneChange = (e) => {
    setName(e.target.value);
  }
  const handleSubmit = (e) => {
    e.preventDefault();
    // 处理表单提交逻辑
  }
  return (
    <form onSubmit={handleSubmit}>
      <label>
        Phone:
        <input type="text" value={phone} onChange={handlePhoneChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default ControlledForm;

2.2 非受控表单的特点和使用场景

非受控表单是指表单元素的值不受 React 组件的 state 或 props 控制,而是将表单数据交给 DOM 节点来处理,可以使用 Ref 来获取数据。特点:

表单元素的值不会保存在组件的 state 中,而是通过 DOM 来获取。

可以通过 ref 来获取表单元素的值,而不需要手动更新 state。

不需要处理 state 的变化,可以减少代码量。

使用场景:

  • 对于简单的表单,不需要对用户输入进行验证和处理。
  • 需要获取表单元素的值进行一些简单的操作,如发送请求或更改 URL 等。
代码语言:javascript
复制
import React, { useRef } from 'react';

 function UncontrolledForm() {
   const nameInputRef = useRef(null);

   const handleSubmit = (e) => {
     e.preventDefault();
     const name = nameInputRef.current.value;
   }

   return (
     <form onSubmit={handleSubmit}>
       <label>
         Name:
         <input type="text" ref={nameInputRef} />
       </label>
       <button type="submit">Submit</button>
     </form>
   );
 }

 export default UncontrolledForm;

2.3 对比受控和非受控表单的差异

特点

受控表单

非受控表单

value 管理

🙆受控表单元素的值保存在组件的 state 中,方便访问和操作

🙅非受控组件需要依赖 ref 来获取元素值,并且会受到组件生命周期变更而影响值

验证和实时性

🙆可以实时验证和处理用户输入

🙅不利于实时反映用户输入的值,不方便对用户输入进行验证和处理

表单的整体控制

🙆对表单数据有更好的控制

🙅对表单数据的控制有限

数据流

🙆可以根据表单元素的值动态地改变其他组件的状态或行为

🙅需要通过 ref 来获取表单元素的值,不符合 React 的数据流思想。

代码复杂性

🙅需要更多的代码来处理表单元素的变化和验证。对于复杂的表单,可能会引入大量的 state 和事件处理函数,导致代码冗长。

🙆代码量较少,不需要处理 state 的变化。对于简单的表单,可以更快地实现功能。

dom更新性能

🙅 频繁的 setState 触发视图的重新渲染可能会导致性能问题。

🙆通过 defaultValue 来设置组件的默认值,它仅会被渲染一次,在后续的渲染时并不起作用

使用场景

基本为最佳实践

一般作为简易实现

3 使用 Hooks 管理 form 的优势

以 ant3 到 ant4 的差异为例

antd3 中form 组件设计思想:

使用HOC(高阶组件)包裹 form 表单,HOC 组件中的 state 存储所有的控件 value 值,定义设置值和获取值的方法

存在缺陷:

由于 HOC 的设计 ,state 存于顶级组件,即便只有一个表单控件 value 值改变,所有的子组件也会因父组件 rerenderrender,浪费了性能

总结:

ant3 时代的 form 可以说“完美”继承了受控表单的缺点,getFieldDecoratorHOC 包裹表单控件的形式,并没有对 Field 自身管理状态。一个表单控件 value 值改变,便会影响整个表单查询渲染

antd4 中 form 组件设计思想:

使用 Context 包裹 form 表单,并在 useForm() 时创建一个 FormStore 实例,并通过 useRef 缓存所有的表单 value 值,定义设置值和获取值得方法。

利用 useRef 的特性,在调用 useForm 的组件中,从创建到销毁等各种生命周期,无论组件渲染多少次,FormStore 只会实例化一次,在每个 Field 中定义 forceUpdate() 强制更新组件。

代码语言:javascript
复制
// rc-form-field
// Field.tsx
public reRender() {
  if (!this.mounted) return;
  this.forceUpdate();
}
.....
public onStoreChange: FieldEntity['onStoreChange'] = (prevStore, namePathList, info) => {
...
case 'remove': {
  if (shouldUpdate) {
    this.reRender();
    return;
  }
  break;
}
case 'setField': {
   if (namePathMatch) {
     const { data } = info;
     // FieldData 处理,touched/warning/error/validate
     ...
     this.dirty = true;  
     this.triggerMetaEvent();
     // setField 时 field 绑定 name 匹配时强制更新 
     this.reRender();
     return;
   }
  // setField 携带 shouldUpdate 的控件时更新
  if (
    shouldUpdate &&
    !namePath.length &&
    requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info)
  ) {
    this.reRender();
    return;
  }
  break;
case 'dependenciesUpdate': {
  /**
  * 当标记了的`dependencies`更新时触发. 相关联的`Field`会更新
  */
  const dependencyList = dependencies.map(getNamePath);
  // No need for `namePathMath` check and `shouldUpdate` check, since `valueUpdate` will be
  // emitted earlier and they will work there
  // dependencies 不应和 shouldUpdate 一起使用,可能会导致没必要的 rerender
  if (dependencyList.some(dependency => containsNamePath(info.relatedFields, dependency))) {
    this.reRender();
    return;
  }
  break;
}
default:
  if (
    namePathMatch ||
    ((!dependencies.length || namePath.length || shouldUpdate) &&
     requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info))
  ) {
    this.reRender();
    return;
  }
  break;

总结:

rc-form-field 中用 useRef 缓存表单状态,使得表单状态不会直接受控件影响,而是在 setField/shouldUpdate/dependenciesUpdate 等逻辑触发时强制更新相依赖的控件,不会造成整个表单重新渲染的过多损耗。另外区别于 ant3HOC 形式包裹的控件,rc-form-field 中提供的独立的 Field 组件概念和对应的 hooks,提供对控件本身直接操作的可能

4 不走寻常路的 react-hook-form

不同于 rc-field-form 中使用的受控表单来做表单状态管理,react-hook-form 使用了 React 的 useRefuseReducer 来处理表单数据的状态,而不是使用 React 的 useState 来追踪表单数据的变化。具备非受控表单的优点以提高性能,并使代码更简洁。 react-hook-form 的最简 demo 如下

代码语言:javascript
复制
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>
    );
}

为什么会说 react-hook-form 提供的是一个非受控表单,其实就需要细究一下这个 ...register 到底返回了什么

代码语言:javascript
复制
// react-hook-form createFormControl
const register: UseFormRegister<TFieldValues>

可以看到 register 返回里并没有 value 字段,那么这个表单控件的值并不受控,state 只存于控件内部,对控件的更新也只会影响自身的更新。

以非受控表单形式实现的 react-hook-form 采用订阅模式来实现不同场景

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 政采云技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2 受控和非受控表单差异
    • 2.1 受控表单的特点和使用场景
      • 2.2 非受控表单的特点和使用场景
        • 2.3 对比受控和非受控表单的差异
        • 3 使用 Hooks 管理 form 的优势
          • antd3 中form 组件设计思想:
            • antd4 中 form 组件设计思想:
            • 4 不走寻常路的 react-hook-form
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档