Formily 是一个数据+协议驱动的表单解决方案,它站在Reactive响应式编程巨人的肩膀上,构建出了从基础表单到低代码领域的高性能通用基础能力,同时其配套的跨框架+跨终端组件生态体系,也能让用户更高效的开发日常业务表单,尽可能的减少了重复冗余的逻辑实现。本篇内容来自白玄在第十六届D2前端技术论坛的分享,将为你介绍如何在高复杂业务场景下提高我们的表单性能与表单开发效率。
附:第十六届D2前端技术论坛现场分享视频
什么是 Formily?
Formily是一款面向中后台复杂场景的数据+协议驱动的表单框架,它也是阿里巴巴集团的统一表单解决方案。借助Formily可以完成复杂的表单页面需求,同时Formily提供的表单设计器可以快速搭建表单。
Formily官网:https://formilyjs.org
1、核心特点:
- 高性能:Formily在字段数量无限多和高频输入场景下,可以保证O(1)时间复杂度;
- 框架无关:Formily已经完美兼容React、Reactive、Vue框架;
- 生态完备:Formily在组件桥接层支持Ant Design、Element、阿里巴巴Fusion组件库;
- 协议驱动:协助Formily让后端通过JSON动态驱动表单渲染以实现表单可搭建可配置;
- 相当完备:Formily可以支持各种复杂的表单页面需求。
2、设计原则
- 单一职责;
- 优雅命名:遵循简单、直观、一致的标准。
3、核心架构
Formily核心架构分为四层,自下而上分别是:
- 内核和协议层:
内核是表单相关UI无关的领域模型系统;
协议层是基于JSON Schema扩展的表单协议; - UI桥接层:基于Formily内核与各种UI框架(如React、Vue)桥接胶水层;
- 组件扩展层:建立在UI桥接层与UI组件库的桥接胶水层;
- 应用层:借助Formily可以构建纯源码、低代码、无代码等应用解决方案;
- Formily开发工具(下图右侧)
Formily核心架构
设计思路
1、高性能思路
问题:普通React用法,想要实现数据收集和同步,需要强依赖虚拟DOM的整树重绘才能达到目的。
目标:不管字段数量多少,高频输入永远都是O(1)复杂度。
方案一、ReactFinalForm/Antd4.0方案
抽象FormState/FieldState,内部使用pub/sub模式进行通讯,脏检查控制联动时的渲染重绘。
时间复杂度:
输入时:Ant Design O(1)/ReactFinalForm O(1)
联动时:Ant Design O(1 or n)/ReactFinalForm O(1 or n)
优点:简单,易上手;
问题:脏检查成本高,外部联动无法做到O(1);
方案二、UForm/Formily1.x方案
抽象FormState/FieldState,内部使用pub/sub模式通讯,抽象主动更新模型,可以解决方案一中外部联动无法做到O(1)的问题。
时间复杂度:
输入时:O(1)
联动时:O(1)
优点:高性能;
问题:学习成本高,外部联动需要转换成主动模型;
方案三、ReactHookForm方案
基于DOM机制收集数据,绕过React受控更新机制,保证高频输入无卡顿。
时间复杂度:
输入时:O(1)
联动时:O(n)
优点:足够轻量,简单;
问题:一旦涉及数据联动,还是需要重绘整树;
方案四、Formily2.0方案
依托Reactive响应式解决方案,构建整个响应式表单领域模型。当组件所依赖的响应式变量发生任何变化,组件都会重新渲染,从而解决组件的脏检查问题。
时间复杂度:
输入时:O(1)
联动时:O(1)
优点:
- 不再需要大量脏检查成本,一切都是基于数据变化精确渲染;
- 既支持主动更新模型,也支持被动响应模型,在多对一被动联动场景,代码量大大减少;
- 外部数据与表单内部字段联动时,只需要定义ApplicationReactiveData即可,代码编写成本很低;
问题:学习成本高。
演示:高频输入性能和外部联动
时间复杂度 O(1) 复杂度,2000个字段,针对单个字段的高频输入,完全无卡顿。
高频输入性能演示
外部联动演示
2、协议驱动思路
问题:如何更高效的开发表单?
目标:定义一套通用协议,简单高效的描述表单UI/逻辑,并对低代码搭建友好。
方案一 纯UITree协议方案,类似于JSON版的JSX
优点
- 足够简单,componentName/props/children完全可以把整个UI结构给描述清楚;
- 对搭建非常友好;
问题:
只能描述视图结构,无法描述状态模型。
方案二 标准JSON Schema方案,参考ReactJSONSchemaForm
优点:
以数据描述视角来驱动UI渲染,基于这样的数据描述,可以将很多状态处理逻辑固化到渲染引擎或者组件内部,从而使得协议层变得非常干净;
问题:
无法解决UI容器包裹问题,对搭建友好。
方案三 Formily2.0方案,针对JSONSchema的扩展协议
核心思路
• 扩展Void类型描述数据无关布局容器或控件;
• 扩展 x- 属性描述UI控件与控件属性;
优点:
• 完全表达UI,无需第二套协议,学习成本很低;
• 可以将临时状态封装在渲染引擎或者组件内部;
• 对搭建更加友好,因为它已经可以把UI层级给表达出来了;
3、扩展组件思路
问题:如何开发协议思路的自定义组件?
目标:足够简单,符合标准组件开发最佳实践。
- 基础扩展方案
属性约定,比如value/onChange组件属性约定,保证所有输入型组件可以快速接入。
优点:
符合统一React属性命名规范标准,对于第三方组件接入成本很低;
缺点:
只能约定标准属性,value/onChange/disabled/readOnly之类的,无法约定渲染引擎内部与扩展组件间其他数据交互。
- 白盒进阶扩展方案
标准Hooks方案,借助ReactContext,就近寻找渲染引擎内部状态模型。
优点:
完全隔离了渲染引擎与组件自身属性,做到了类型更友好,写法更自然的通信模式;
缺点:
对接三方组件每次都需要单独写一个包装组件,不方便快速桥接。
- 黑盒进阶扩展方案
连接器扩展,与白盒进阶扩展不同的是黑盒用connect和mapProps做了一层包装。
优点:
• 无需重新封装组件,只需要关注属性与字段模型的映射;
• 映射转换逻辑方便复用;
缺点:
学习理解成本高。
- 高级场景化扩展方案
渲染权限转移模板化方案。Formily提供RecursionField组件负责动态渲染子树。
优点:
可以封装出大量模板组件,模板组件内部可以将布局、交互、逻辑全部固化,在实际使用过程中提效显著,比如ArrayTable/ArrayCard这种自增组件;
缺点
学习理解成本高。
综上所述,Formily的扩展能力是建立在标准组件开发最佳实践基础之上,是没有任何黑魔法的。
使用数据
1、使用数据情况
如下图所示,Formily被广泛应用于阿里系和非阿里系的各种BU的不同场景。
2、社区运营数据
未来规划
1、规划
- 进一步优化性能;
- 构建更多的扩展生态;
- 支持React18;
- 思考如何借助Formily一次性解决CRUD页面;
2、不足
• 不兼容IE,且无法解决;
• 整体体积较大,压缩前130k左右,压缩后80k左右,包含桥接组件库;
• 学习成本比较高,计划逐步完善视频教程。
小结:Formily 的价值
Formily在高性能的加持下,实现一套底层适配多个框架,一份Schema适配多端(PC、手机),一个生态覆盖全场景(Pro/Low/No Code)。