选择的原因

公司本身使用reduxForm处理表单,也讨论过使用antd的表单,思考许久有以下问题:

reduxForm

不排除我对reduxForm理解不够深

  1. reduxForm已经停止维护,表单过重,打包体积过大(24kb),react-hook-form 8k

  2. reduxForm处理联动逻辑比较麻烦,没有一个可靠的form实例

  3. reduxForm联动改变数值不触发校验,手动触发校验方式比较hack(针对每个Form组件拿到实例手动触发onBlur校验),其次校验render时机不正确,导致错误文案没法展示,项目中使用setTimeout解决

  4. reduxForm文档比较难读,对Typescript支持不友好

  5. reduxForm是采用高阶组件,对逻辑复杂的表单的onChange会有性能问题(卡顿,输不了值),目前已经有FormComponent出现此问题

antd

  1. antd封装了一个完整的解决方案,底层使用rc-field-form,假如我们在rc-field-formForm这个过程需要自定义业务,难以实现,并且有很多不需要的功能

  2. antd的表单再去封装动态表单难以实现,例如基于antdForm去封装一个ant-design-proProForm成本会激增

  3. antd的功能如果没有你想要的,无法去表单内部实现,只能通过其他方式,例如: reduxForm会对表单组件传递dirty判断数据是否被修改、valid判断校验是否通过(不触发render的情况下)

  4. antd是属于另一个ui库,与我们初始的ui选型不一致,无法单独引入Formantd的样式采用module css的方式,与material-uicss in js的方式相违背,未来在主题上会出现很多问题

实现的目标

静态表单

表单数据都是以组件形式自己编写

1
2
3
4
5
<Form>
<Form.Item name="xx">
<FormComponent />
</Form.Item>
</Form>

目标:

借鉴antd,保留原项目reduxForm中好的东西,剔除antd不需要的功能,每一步流程都可以根据业务自定义

对项目的Form表单实现统一校验、统一联动校验、统一/自定义样式布局、类似于antd中常用的基础功能

保留一定的可扩展性未来支持骨架屏,处理异步逻辑等等

动态表单

表单数据以JSON schema的格式配置,统一渲染

1
2
3
4
5
6
7
8
9
10
11
12
const formField = [
{
type: 'Input'
name: 'xxx',
label: 'xxx',
property: {
...// 属性
}
}
]

<HookForm formField={formField} />

动态表单解决的问题:

  1. FormComponent过多且布局较为统一

  2. 表单数据以JSON Schema的形式存储在后端

目标:

功能上支持自定义校验、自定义组件配置、联动逻辑、表单动态的props传递

静态表单实现方式

流程

Form

  1. 处理用户对表单的全局监听,以及hook-form中提供的内置方法

  1. 处理contextValue,需要传递给Form.Item的全部放在contextValue下

  1. 根据用户传递的ref,创建实例,抛出实例方法

Form.Item

  1. 渲染FormComponent时对每一个FormComponent增加默认样式(FieldWrap)

  1. 针对没有提供nameFormComponent只渲染样式,数据不受hook-form控制

  2. 为用户提供单个FormComponent的监听(直接在Form.Item上传递onChange)

  3. 处理表单校验(默认校验、自定义校验),提供自定义校验会覆盖默认校验

  1. hook-form保存的value传递到表单组件用于受控,将改变hook-form``FormComponentonChange传递到对应表单(即受控组件常用的value和onChange)

动态表单实现方式

流程

HookForm

  1. onTrigger作为回调传递给FieldRenderonChange, 针对表单的每个onChange会判断是否满足联动逻辑

    FieldRender

  2. 处理每个表单的统一校验,针对单个FormComponent的用户自定义校验

  3. 获取FormComponentprops, 包括了JSON schemaproperty属性下的、开发者privateProps以及全局的publicProps

动态表单validate.ts

校验相关

自定义表单组件

编写方式

统一规定受控组件, 使用TypeScript编写

  1. onChangehook-formonChange, 用于改变表单的值

  2. valuehook-form数据池中的数据

FieldMap

validate.ts

TypeScript支持

  1. 对应自定义的FormComponent的类型

  1. 对应Form实例Type

不足与改进

  1. 由于二级表单形式五花八门,不支持二级表单,目前也没想好怎么支持

  2. 默认的表单数据的类型应该作为泛型传入,而不是内置HookFormData传入,这样导致submit返回的类型与外部定义的类型不兼容,需要类型断言。写完后才发现,看了antdForm也是那么做的,但是改动量过大

  3. 假如Button的按钮提交根据是否校验通过是否修改过默认表单数据来判断是否disable,目前只能render<Form></Form>组件的最后,不能自定义渲染位置。reduxForm是采用高阶组件的模式,会渲染全表单,与hook-form设计思想违背,hook-form将每个FormComponent与自身数据池隔离提升性能。目前也没有想到最优解

  4. defaultFormData可能是异步的,目前必须在无数据的时候不渲染Form,不然默认值设置不上去, 如下:

    1
    2
    // 假如 asyncDefaultFormData 是异步获取的
    !asyncDefaultFormData ? null : <Form defaultFormData={asyncDefaultFormData} />