选择的原因
公司本身使用reduxForm
处理表单,也讨论过使用antd
的表单,思考许久有以下问题:
reduxForm
不排除我对reduxForm
理解不够深
reduxForm
已经停止维护,表单过重,打包体积过大(24kb),react-hook-form
8kreduxForm
处理联动逻辑比较麻烦,没有一个可靠的form
实例reduxForm
联动改变数值不触发校验,手动触发校验方式比较hack
(针对每个Form
组件拿到实例手动触发onBlur
校验),其次校验render
时机不正确,导致错误文案没法展示,项目中使用setTimeout
解决reduxForm
文档比较难读,对Typescript
支持不友好reduxForm
是采用高阶组件,对逻辑复杂的表单的onChange
会有性能问题(卡顿,输不了值),目前已经有FormComponent
出现此问题
antd
antd
封装了一个完整的解决方案,底层使用rc-field-form
,假如我们在rc-field-form
到Form
这个过程需要自定义业务,难以实现,并且有很多不需要的功能antd
的表单再去封装动态表单难以实现,例如基于antd
的Form
去封装一个ant-design-pro
的ProForm
成本会激增antd
的功能如果没有你想要的,无法去表单内部实现,只能通过其他方式,例如:reduxForm
会对表单组件传递dirty
判断数据是否被修改、valid
判断校验是否通过(不触发render
的情况下)antd
是属于另一个ui
库,与我们初始的ui
选型不一致,无法单独引入Form
,antd
的样式采用module css
的方式,与material-ui
的css in js
的方式相违背,未来在主题上会出现很多问题
实现的目标
静态表单
表单数据都是以组件形式自己编写
1 | <Form> |
目标:
借鉴antd
,保留原项目reduxForm
中好的东西,剔除antd
不需要的功能,每一步流程都可以根据业务自定义
对项目的Form
表单实现统一校验、统一联动校验、统一/自定义样式布局、类似于antd中
常用的基础功能
保留一定的可扩展性未来支持骨架屏,处理异步逻辑等等
动态表单
表单数据以JSON schema
的格式配置,统一渲染
1 | const formField = [ |
动态表单解决的问题:
FormComponent
过多且布局较为统一表单数据以
JSON Schema
的形式存储在后端
目标:
功能上支持自定义校验、自定义组件配置、联动逻辑、表单动态的props
传递
静态表单实现方式
流程
Form
- 处理用户对表单的全局监听,以及
hook-form
中提供的内置方法
- 处理
contextValue
,需要传递给Form.Item
的全部放在contextValue下
- 根据用户传递的
ref
,创建实例,抛出实例方法
Form.Item
- 渲染
FormComponent
时对每一个FormComponent
增加默认样式(FieldWrap
)
针对没有提供
name
的FormComponent
只渲染样式,数据不受hook-form
控制为用户提供单个
FormComponent
的监听(直接在Form.Item
上传递onChange
)处理表单校验(默认校验、自定义校验),提供自定义校验会覆盖默认校验
- 讲
hook-form
保存的value
传递到表单组件用于受控,将改变hook-form``FormComponent
的onChange
传递到对应表单(即受控组件常用的value和onChange)
动态表单实现方式
流程
HookForm
onTrigger
作为回调传递给FieldRender
的onChange
, 针对表单的每个onChange
会判断是否满足联动逻辑FieldRender
处理每个表单的统一校验,针对单个
FormComponent
的用户自定义校验获取
FormComponent
的props
, 包括了JSON schema
中property
属性下的、开发者privateProps
以及全局的publicProps
动态表单validate.ts
校验相关
自定义表单组件
编写方式
统一规定受控组件, 使用TypeScript
编写
onChange
是hook-form
的onChange
, 用于改变表单的值value
是hook-form
数据池中的数据
FieldMap
validate.ts
TypeScript支持
- 对应自定义的
FormComponent
的类型
- 对应
Form
实例Type
不足与改进
由于二级表单形式五花八门,不支持二级表单,目前也没想好怎么支持
默认的表单数据的类型应该作为泛型传入,而不是内置
HookFormData
传入,这样导致submit
返回的类型与外部定义的类型不兼容,需要类型断言。写完后才发现,看了antd
的Form
也是那么做的,但是改动量过大假如
Button
的按钮提交根据是否校验通过和是否修改过默认表单数据来判断是否disable
,目前只能render
在<Form></Form>
组件的最后,不能自定义渲染位置。reduxForm
是采用高阶组件的模式,会渲染全表单,与hook-form
设计思想违背,hook-form
将每个FormComponent
与自身数据池隔离提升性能。目前也没有想到最优解defaultFormData
可能是异步的,目前必须在无数据的时候不渲染Form
,不然默认值设置不上去, 如下:1
2// 假如 asyncDefaultFormData 是异步获取的
!asyncDefaultFormData ? null : <Form defaultFormData={asyncDefaultFormData} />