中后台业务开发(一)「表单原理」

1. 前言

读完本文你会收获到:

  1. Form 原理最佳实践,提高日常开发效率。
  2. Form 源码中运用了哪些设计模式,丰富自己编码的武器库。

2. 原理

一句话概括:数据层面,单个实例统一管理所有表单数据,通过事件订阅和通知机制进行更新与响应,更新时触发相应组件重新渲染。

2.1. Form与Item

2.1.1. 一个实例

每一个 Form 组件都有相应的实例对象,集中管理表单状态、校验规则等。大家最常看到的应该就是如下函数:

const [form] = Form.useForm()

这个函数的最基础的作用是 创建表单实例,实例中包含状态仓库以及对状态的增删查改的方法,以及事件的订阅与通知。

2.1.2. 订阅与通知

看两段简化的代码:

js 复制代码
private fieldEntities = [];

// 将Item实例存在Form实例中
private registerField = (entity) => {
  // 存储子项实例
  this.fieldEntities.push(entity);
  ...
}

// 通知Item
private notifyObservers = (
  ...
) => {
  ...
  this.getFieldEntities().forEach(({ onStoreChange }) => {
    onStoreChange(...);
  });
  ...
};
js 复制代码
class Field extends ... {

  public componentDidMount() {
    ...
    // Item将实例注册到form实例中
    this.cancelRegisterFunc = registerField(this);
    ...
  }

  // 提供给form实例发送通知的回调函数
  public onStoreChange = (prevStore, namePathList, info) => {
    ...
    switch (info.type) {
      case 'reset':
        ...
      case 'remove':
        ...
      case 'setField': 
        this.reRender();
        ...
      case 'dependenciesUpdate':
        ...
      default:
        ...
        break;
      }
    ...
  };

	// 更新class类型的Item组件
	public reRender() {
    ...
    this.forceUpdate();
  }
}

从上面的两段简化后的代码可以清晰的看出:

在form实例中一旦有状态的变更只需要遍历Item实例的 onStoreChange ,就可以触发 Item 组件的 update。

而form实例中调用的 onStoreChange 方法实际上是在使用 form实例 的 registerField 注册item实例后,item实例中的方法

但是还是有几个细节需要交代一下:

  1. Item实例 怎么调用 form实例registerField 方法把自身给注册进去?

Item 是 Form 的子组件,在 Form 组件中通过 Context 的方法将 form 实例注入到任何层级的子组件中。所以在 Item 组件中,因为是在 Form 的包裹中,所以自然可以通过 useContext 拿到 form 实例,从而使用 registerField 将自身注册到 form 实例中。

  1. form实例中统一管理的数据如何与Item中的受控组件进行数据传递的呢?

下一节,Item与受控组件。

2.2. Item与受控组件

我们自定义受控组件或是使用通用组件,组件的 props 一般都会尽量遵循:

const { value, onChange, ... } = props

获取传入的 value 渲染组件,通过 onChange 回调函数的方式将组件变更的数据传递到上层使用。

我们来看Item组件中如何传递 value 和消费 onChange 回调的。

2.2.1. value

value比较简单,只需要Item组件中通过form实例中的方法(见上文),在数据仓库中查找对应字段的值即可。

ini 复制代码
public getValue = (...) => {
    const { getFieldsValue }: FormInstance = this.props.context;
    const namePath = this.getNamePath();
    ...
  };

2.2.2. onChange

还是在Item组件中,通过form实例中的方法 dispatch,将更新的值传递到form实例中,我们一起看一下 dispatch 方法:

js 复制代码
//使用
onChange = (...args) => {
	...
  dispatch({
    type: 'updateValue',
    namePath,
    value: newValue,
  });
  ...
}


private dispatch = (action) => {
    switch (action.type) {
      case 'updateValue': {
        const { namePath, value } = action;
        this.updateValue(namePath, value);
        break;
      }
      ...
      default:
      // Currently we don't have other action. Do nothing.
    }
  };

private updateValue = (name: NamePath, value: StoreValue) => {
    const namePath = getNamePath(name);
    const prevStore = this.store;
    this.updateStore(setValue(this.store, namePath, value));

    this.notifyObservers(prevStore, [namePath], {
      type: 'valueUpdate',
      source: 'internal',
    });
    ...
  };

可以看到当某一个Item的值发生改变时,首先 会更新form实例的数据仓库,然后 看看有没有字段依赖了当前更新的字段, 通知各订阅了消息的Item实例,最后再将 value 和 onChange 注入到子组件的props中完成闭环。

3. 源码中的设计模式

3.1. 观察者模式

3.1.1. 源码案例(见[2.1.2. 订阅与通知](#3.1.1. 源码案例(见2.1.2. 订阅与通知) "#h212"))

3.1.2. 解析

观察者模式提供了一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

结合观察者模式的特点,我们在日常的编码中:

  1. 目前很多复杂的后台编辑项,经常会有不同项联动的效果,开发者就可以考虑优先使用Form表单自带的观察者模式架构去实现联动。
  2. 后台的表单只是观察者模式的实践之一,结合观察者模式 的抽象概念,有很多业务场景的功能可以考虑使用这种架构去组织代码,例如:购物车场景 。有多个组件依赖数据仓库 (购物车),数据仓库的变更会通知 各组件。大多数的真实场景购物车的初始数据可能是由后端下发的,其实也就是一次修改数据仓库的行为,包括用户的交互。而用户的交互等行为修改数据仓库后,统一通知各订阅单位 ,包括上传购物车数据至后端,也可以看作是后端的对数据仓库的订阅

在不同的场景中,观察者模式帮助实现了行为实体间的解耦,使得一个实体的变化可以通知到其他关联的实体。

3.2. 单例模式

3.2.1. 源码案例

ini 复制代码
function useForm<Values = any>(form?: FormInstance<Values>): [FormInstance<Values>] {
  const formRef = React.useRef<FormInstance>();
  ...
  
  if (!formRef.current) {
    if (form) {
      formRef.current = form;
    } else {
      ...
      const formStore: FormStore = new FormStore(forceReRender);

      formRef.current = formStore.getForm();
    }
  }
  ...
  return [formRef.current];
}

3.2.2. 解析

上面的代码中通过 useForm hook,确保表单状态的唯一性,以避免不同实例之间的状态冲突。

单例模式和观察者模式在管理全局状态、资源、事件等比较适合在一起使用,两个模式的概念能够比较好的融合,观察者模式一对多的对象关系,那中心数据仓库就可以使用单例模式管理起来,提供了一种可维护和解耦的方式。

4. 总结

在中后台的业务开发中,表单必不可少,合理的借用表单组件的设计模式来组织自己的代码结构是我们需要上的第一课。

脱离表单原理的视角,抽象后的设计模式,能让我们在未来的开发中,又多了一件趁手的武器

后续的这个中后台系列的文章都会在为大家讲解原理的基础 上,深入考究在设计模式/代码结构上有哪些值得借鉴学习的地方。

还有哪些模块 值得我们一起学习讨论 欢迎在评论区中留言

长话短说,只讲干货,我们下期再见!

最后

📚 小茗文章推荐:

关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~

相关推荐
yunduor9092 小时前
从零开始搭建UVM平台(九)-加入reference model
前端
莘薪2 小时前
HTML的修饰(CSS) -- 第三课
前端·css·html·框架
某公司摸鱼前端4 小时前
uniapp 上了原生的 echarts 图表插件了 兼容性还行
前端·uni-app·echarts
2401_857297914 小时前
秋招内推--招联金融2025
java·前端·算法·金融·求职招聘
BHDDGT4 小时前
react-问卷星项目(5)
前端·javascript·react.js
小白求学16 小时前
CSS滚动条
前端·css
与妖为邻6 小时前
房屋水电费记账本:内置的数组数据击按钮不能删除,页面手动添加的可以删除
前端·javascript·css·html·localstorage·房租水电费记账本
miniwa6 小时前
JavaScript 中最快的循环是什么?
前端·javascript
阳树阳树7 小时前
Websocket 基本使用
前端·javascript·面试
liangshanbo12158 小时前
将 Intersection Observer 与自定义 React Hook 结合使用
前端·react.js·前端框架