关于表单的探讨
MuYunyun opened this issue · 0 comments
出发点
中后台项目中表单可以说是最重要的一环, 基本上中台的项目都是在书写各种各样的表单。如何提高开发效率是 daform 正在探索和实践的点。
- 重复性。
- 动态表单。每次遇到动态表单的需求都是要花大精力进行, 而且与 redux 耦合性较强, 开发体验十分不好。
设计理念
- 双向绑定
- 栅格化布局
- 错误逻辑集中化管理
- 动态表单的解决方案
- 不依赖第三方状态管理库
- 自由搭配第三方 UI 组件库
可以像如下这样使用
import React from 'react'
import { Input } from 'antd' // 可以是其它 UI 库
import { Form, FormItem } from 'daform'
@Form()
class Demo1 extends React.Component {
render() {
return (
<>
<FormItem name="name" label="姓名"><Input /></FormItem>
<FormItem name="age" label="年龄"><Input /></FormItem>
</>
)
}
}
表单静默刷新 vs 双向绑定
如果表单间的数据无需联动, 差不多就是表单数据显示 => 全局对象
的单向映射的模型, 这样子只要处理好这一方向的流动性就好了。但是一旦表单间的数据需要联动, 这就涉及到表单 <=> 全局对象
的双向映射了(表单间某一数据发生变动通知全局对象, 全局对象改变另一数据变化, 呈现到表单对应数据上)。需要一种机制来在当前的 React 表单中实现双向绑定。借助 React 16.3 出来的 Context 机制, 实现了一个伪双向绑定的表单。
双向绑定
目前表单中修改的数据已经实时同步到内部维护的 formData
对象中, 但假若要使用 setFormItem(itemName, value)
向 formData 写入或修改数据后, 页面应当进行重新渲染, 这时候可以考虑使用观察者模式进行处理。但这个 api
当下的需求用到其实不是特别多, 这部分可以日后实现。
目前用
initialValue
能开发大部分需求。
关于 Context 使用
每当Provider的值发送改变时, 作为Provider后代的所有Consumers都会重新渲染。 从Provider到其后代的Consumers传播不受shouldComponentUpdate方法的约束,因此即使祖先组件退出更新时,后代Consumer也会被更新。
因为表单有联动的需求, 所以重绘表单是必要的。但是处于性能的考虑只重新渲染有变动的表单是十分必要的。结合 Context 的机制, 选择了在 Context 下面使用 PureComponent。
避免表单多次重绘
因为使用 React.cloneElement()
创建 React 对象传入子组件, 所以导致子组件每次都会重新渲染, 因此使用单例模式在内存里将创建的 React 对象进行缓存, 从而避免了多次重绘。目前的设计是仅当 disable 属性发生变化时, 才会再次调用 React.cloneElement()
。
栅格化布局
借助 sass/less 的语法能动态快速生成 24 栅格化布局。如下以 less 为例生成相应的布局样式
.generate-columns(24);
.generate-columns(@n, @i: 1) when (@i =< @n) {
.col-@{i} {
width: (@i * 100% / @n);
}
.generate-columns(@n, (@i + 1));
}
错误逻辑集中化管理
错误捕获使用了 async-validator, 个人倾向于将一个表单的错误逻辑放在一个文件里进行管理。
表单存在的状态形式
难免可视状态和编辑状态的字段是会有些微小的地方存在不一致, 基于这样的思考🤔, 暂时将表单的状态只分为编辑态
和禁用态
。
动态表单
提供一个动态表单组件 <Dynamic>
import React from 'react'
import { Input } from 'antd'
import { Form, FormItem } from 'daform'
@Form()
class Demo1 extends React.Component {
render() {
const { form } = this.props
return (
<Dynamic>
<FormItem name="name" label="姓名"><Input /></FormItem>
<FormItem name="age" label="年龄"><Input /></FormItem>
</Dynamic>
)
}
}
表单里面所有数据结构(包括动态表单)都是扁平化的, 动态表单数据结构类似:
{
"name0": "deku",
"age0": "12",
"name1": "siren",
"age1": "13",
"name2": "diana",
"age2": "11"
}
测试用例
框架选择了 Jest + Enzyme;
测试应该覆盖到的几个点:
- Form
- Form 中的 data
- FormItem
- 传入 FormItem 的各个属性的校验
- 渲染次数的校验