化繁为简,开箱即用 | 阿里经济体Formily开源表单方案建设

简介: Formily谐音family,集标准表单描述Schema协议、表单渲染、表单组件及周边生态一体的统一表单解决方案。

作者:云数

image.png

Formily谐音family,集标准表单描述Schema协议、表单渲染、表单组件及周边生态一体的统一表单解决方案。

背景

纵观近几年前端技术发展方向,以及延伸到未来前端发展方向,我们可以总结为以下几个阶段:F2V(Function函数驱动视图,以jQuery为代表),D2V(Data数据驱动视图,以MVVM框架React、Angular、Vue为代表),C2V(Component组件驱动视图,以Ant Design、Fusion Design为代表),T2V(Template模板驱动视图,以Ant Design Pro等为代表),S2V(Scene场景驱动视图),M2V(Model模型驱动视图)以及 AI2V建设(AI智能驱动视图,比如以ImageCook为典型代表)。各个阶段从下至上环环相扣,紧密依赖,虽然各层在同一时期都有建设,但最终上层建设的完善离不开下层建设的基础。

image.png

在组件化、模板化建设趋于完善的状态下,通过特定场景(例如表单、列表等)进行提效是当下中后台建设的关键,即当前处于S2V场景驱动视图建设的关键时期。能否建立一套标准方案,使协议通、物料通、能力通,是开箱即用SDK战役核心要解决的问题,而在中后台业务中,表单是任何业务都避免不了的一个基本需求。在当前表单方案需求多、效率低、性能差,各BU业务都在重复性建设的背景下,表单核心要实现的目标如下:

  1. 统一集团表单解决方案,具备高可用、高性能、高扩展性等特点,避免重复建设;
  2. 建立一套标准表单Schema描述协议,提升表单开发效率及动态化需求,为未来自动化、智能化提供协议基石;
  3. 统一表单Schema协议渲染引擎,生态表单组件无缝接入,提供自定义组件机制及接入能力;
  4. 不断完善表单开发周边工具生态,降低表单开发门槛,提升表单开发效率。

整体方案基于UForm(现已更名为Formily)为建设蓝本,在协议层进行了标准化约定及扩展、扩展了工具层,渲染层和表单层进行了细节方案设计及代码重构工作。UForm以其分布式状态管理的高性能特点以及在开源社区的良好口碑,为表单共建提供了有利条件。

整个表单方案建设人员包含了阿里经济体绝大部分BU,包括淘系、供应链、数据技术及产品技术部、飞猪、口碑、CBU、ICBU、CCO、CRO、GOC、阿里云、阿里妈妈、优酷大文娱等。这些同学在各BU基本代表了表单方向的核心负责人,在共建方案上把过往的一些宝贵经验分享出来,同时投入到统一方案建设当中,众多人员参与其中,这在其他建设上也是很少见的。

整体架构

基于上述目标,整个表单方案分4层建设,整体架构图如下:

image.png

4层建设分别为协议层、工具层、渲染引擎及表单组件层协议层主要定义了表单的描述协议,有10+协议规范,部分仍在提案中(比如布局方案),完备的描述能力,后面会有详细描述。工具层是指在协议的基础上,提供一个可视化的协议生成工具,具备schema快速生成、可视化配置及实时预览功能,工具层实际是一个react组件,最终会以@formily/react-schema-editor NPM包形式对外提供能力。渲染引擎是基于标准schema协议的渲染实现,所有在协议层定义的能力都将在这一层实现,同时能够对react生态表单输入组件及自定义组件无缝接入,同样以@formily/react-schema-renderer NPM包的形式提供。表单组件层是渲染层实现的核心依赖,表单组件层负责表单消息通信、状态管理及副作用的逻辑处理,这一层对上层业务一般可以不关心,以@formily/core 包向上层输出能力。

适用场景

在业界已有很多表单方案的情况下,什么情况适用Formily表单呢?

  • 表单项不固定,对动态表单渲染有需求;
  • 表单项特别多,对渲染性能有严苛要求;
  • 对现有的表单组件不满意,寻求更好的解决方案。

建设方案

协议层

在表单这一特定场景下,表单描述具备高度可抽象的能力。表单标准描述协议,旨在通过对表单领域描述抽象,提供一份标准描述协议,以满足协议驱动以及动态渲染表单需求,降低表单开发成本,同时为未来自动化、智能化奠定协议基础。表单核心要素包含2部分:数据 + UI,在前期大量实践方案及调研的背景下,通过 JSON Schema 官方标准 + UI Schema描述,实现对表单、表单项、嵌套、循环、校验、联动、异步、组件扩展及布局等能力。

image.png

对于前端同学来说,JSON Schema并不陌生,这里不对JSON Schema做过多介绍,表单协议对于数据描述强依赖JSON Schema。在JSON Schema外,通过x-的扩展方式,抽象了4个属性以对表单UI进行描述,现对这4个属性进行解释。

  • x-props:字段属性描述,通俗可以理解为FormItem属性定义;
  • x-rules: 字段校验描述,Array类型,支持通用的必填、正则校验、函数校验以及错误信息提示;
  • x-component:字段编辑组件类型,比如Input、Select等,另外仍然可以是CustomComponent,通过渲染层注入组件即可;
  • x-component-props:编辑组件属性,这个应该很好理解,不过多解释。

需要说明的一点是,无论是x-props还是x-component-props,我们并不对其内部的属性进行约束,内部属性描述完全取决于组件库或自定义组件的属性,这部分无法枚举,也没法做到统一,因为不纳入协议规范中。

基础协议

基于此协议规范,对于最基本的表单、表单项描述,我们举一个最简单的例子:

{
    "type": "object",
    "title": "表单标题",
    "properties": {
        "name": {
            "type": "string",
            "title": "姓名",
            "default": "淘小宝",
            "x-props" : {
                "extra": "不得超过6个字符",
                "labelCol": {
                    "span": 6
                },
                "wrapperCol": {
                    "span": 18
                }
            },
            "x-component": "Input",
            "x-component-props": {
                "disabled": true,
                "placeholder": "请输入姓名"
            },
            "x-rules": [{
                "required": true
            }]
        }
    }
}

该Schema描述定义了一个简单表单,其中只有一个可以输入姓名的表单项,输入组件为Input,该表单项有一个默认值“淘小宝”...等,其他通过字面都能理解。

表单嵌套、数组定义

表单嵌套、数组定义等都可以基于JSON Schema进行相应的扩展,在Formily整体解决方案中以自定义组件的方式实现,当然官方会提供一套标准嵌套、数组的组件渲染,业务上如果不满足需求,可以进行自定义实现。

联动协议

对于联动协议的描述,在我们的协议制定过程中争论了很长时间,目前达成的一致意见是,通过表达式的方式进行联动描述,表达式以{{}}进行描述,通过渲染层注入root.value(表单值)、root.schema(schema描述)、root.context(上下文)进行解析。

举个例子:

{
    "type": "object",
    "title": "表单标题",
    "properties":{
        "name": {
            "type": "string",
            "title": "姓名",
            "x-component":"Input",
            "x-component-props":{
                "onChange": "{{root.context.fields.setFieldState('age', '')}}"
            }
        },
        "age": {
            "type": "string",
            "title": "年龄",
            "x-component":"NumberPicker",
            "x-component-props":{
                
            }
        }
    }
}

联动表达式中我们通过获取到上下文中的fileds对象,可以设置另外一个字段的任意属性,比如value值、是否禁用/显示隐藏等,实现了联动的处理。这种方式属于主动触发的方式实现,对于一些对性能要求比较高的场景适用。未来希望通过更简单的方式来进行联动描述,即当前字段的值会受什么字段影响,属于被动监听的方式。例如bar字段的值由foo字段决定,是否禁用也由foo字段决定,你可以这样写:

"bar": {
    "type": "string",
    "title": "Bar",
    "x-component":"Input",
    "x-component-props":{
        "value": "{{root.value.foo < 0 ? 'negative' : 'positive'}}",
        "diabled": "{{root.value.foo < 0}}"
    }
}

布局协议

布局协议描述目前仍处于草案阶段,关于布局协议在之前也曾多次讨论到,是否有必要重新定义一套关于布局的协议是比较有争议的点,当然也是我们尽量避免。本身JSON是树形结构,对于描述页面布局具有天然的优势,借助当前的数据描述进行扩展,通过扩展形式标记当前节点代表UI布局,就可以实现对于布局的描述。目前的方案是,沿用当前的协议,通过扩展如 x-skip/x-hide/x-visible以满足对于布局的需求。

举个例子:

image.png

该Schema中定义了一个表单,有姓名和年龄两个表单项,这两个表单项是横向排列,一行两列布局。通过x-skip进行标记,这样在表单进行取值时取到的是{"name":"淘小宝", "age": 18},而不是如下这样的值:

{
    "row" : {
        "col1": {
            "name":"淘小宝",
        },
        "col2": {
            "age": 18
        }
    }
}

工具层

标准Schema协议最终是给机器消费的,对于开发者来说并不友好,因此在标准协议基础上我们实现了Schema生成工具 @formily/react-schema-renderer ,可以非常方便、快速进行Schema配置编辑。Schema生成工具核心能力包含4个:

  • json2Schema快速生成:通过一段数据快速生成最基本的Schema,然后进行属性配置;
  • 可视化配置:内置Antd 和 Fusion两大组件库表单组件属性,输入自动提示;
  • 源码编辑及实时预览:可以对Schema进行源码编辑及渲染表单实时预览;
  • 第三方平台接入:以NPM组件的形式,第三方可以快速进行进入。

image.png
image.png

渲染层

渲染层@formily/react-schema-renderer主要对标准协议规范进行解析,以实现表单渲染。

核心有3个组件:SchemaForm、SchemaField、SchemaMarkup

SchemaForm核心渲染引擎组件,具体功能如下:

  • 标准表单Schema协议解析
  • 表单输入组件注册
  • 初始化设置(value/defaultValue)
  • 事件监听(onSubmit/onChange)
  • 副作用处理(effects)
  • 通信管理(actions)

SchemaField指某一个表单项的实现,等价于标准协议中对每一个字段的描述信息。

SchemaMarkup是指对于布局协议的实现。

限于篇幅,这里不对具体组件实现做过多描述,具体原理可以参考源码。

下面来看一个具体使用的例子:

注:该例子只是用来阐述能力,实际中并不会有两个组件库同时使用的情况。

image.png

例子中实际有两种组件,一种是以value/onChange约定实现组件值输入输出方式,比如Input、Select,另外一种是非value/onChange约定的组件,比如Progress、CheckBox等。按照value/onChange约定在SchemaForm中可以直接注册使用,其他情况则需要进行connect设置valueName的key等。这样实现的好处在于,对于按照约定实现的组件,可以无缝接入(自定义组件也建议实现此约定)。非按照约定实现的组件,仍然可以通过connect的方式注册到表单体系中来。

注:组件注册方式及实现仍在讨论中,近期公布具体方案。

表单组件层

要探究Formily表单组件高性能的原因,需要深入了解Formily在底层的实现。Formily的核心实现在@formily/core这一层,相较于其他表单基于全局状态管理,单项数据流动的理念,Formily在其实现上主要有两点区别:字段状态分布式管理及面向复杂通用组件的通讯管理方案。

image.png

如上图所示,除全局表单状态FormState外,每个字段都维护了一个FieldState,通过内置的路径系统就可以找到对应的字段实例,然后通过setFieldState API更改单个字段状态(具体原理可以参考这篇文章,10分钟解读UForm路径系统)。

我们可以通过2个动图来展示全局状态管理以及分布式状态管理的差异,也就不难理解为什么Formily比其他方案性能更好的原因了。

640.gif
640 (1).gif

总结&未来规划

无论从协议制定,工具层、渲染层及表单组件层建设,项目组成员在各自繁重的业务之余积极加入到共建项目中来,进行协议规范制定、方案讨论及代码开发,在此致敬感谢项目组每个同学的付出。现阶段在协议层、工具层及底层表单组件层基本建设完成。为给用户最好的体验,在渲染组件层仍然有一定的改造工作量,项目组也在积极协调开发中。接下来一段时间重点将在整体链路联调、文档建设以及开源发布上。未来我们将进行Formily在集团内业务的试点落地,同时进行开源运作推广。

官网地址:https://formilyjs.org/
Github开源项目地址:https://github.com/alibaba/formily

在表单使用过程中的任何问题或建议欢迎通过issue联系我们,也可以加我微信(微信号:Andy_Hit)私下交流。

image.png


image.png
关注「Alibaba F2E」
把握阿里巴巴前端新动向

相关文章
|
JSON 前端开发 JavaScript
开源表单方案 Formily 的核心设计思路
Formily 是一个数据+协议驱动的表单解决方案,它站在Reactive响应式编程巨人的肩膀上,构建出了从基础表单到低代码领域的高性能通用基础能力,同时其配套的跨框架+跨终端组件生态体系,也能让用户更高效的开发日常业务表单,尽可能的减少了重复冗余的逻辑实现。本篇内容来自白玄在第十六届D2前端技术论坛的分享,将为你介绍如何在高复杂业务场景下提高我们的表单性能与表单开发效率。
5517 1
开源表单方案 Formily 的核心设计思路
|
缓存 NoSQL Java
【JetCache】JetCache的使用方法与步骤
【JetCache】JetCache的使用方法与步骤
6999 1
|
数据可视化 安全 搜索推荐
探析低代码开发平台的核心能力
探析低代码开发平台的核心能力
373 0
|
资源调度 前端开发 算法
前端依赖版本重写指南
感谢神奇的 Semver 动态规则,npm 社区经常会发生依赖包更新后引入破坏变更的情况(应用没有使用依赖锁的话),而应用开发者就要在自己的依赖声明里先临时绕过,避免安装到有问题的版本,如果是一级依赖,只需要改 package.json 的声明就可以了,但如果是子依赖,就需要进行版本重写(overrides/resolution)了。本文是一篇针对版本重写功能的指南性文章,当你遇到如下的问题时,就可以按照对应的依赖重写语法,解决这些依赖问题了。
7376 1
前端依赖版本重写指南
|
JSON 移动开发 JavaScript
多款顶级好用的 Vue 表单设计器测评推荐,可拖拽生成表单
Vue 前端开发中,表单组件是排在前三的高频使用的组件,如何快速构建表单,节省力气,避免重复造轮子呢,选择一款适合自己的前端表单设计器就非常重要了。本文介绍 4 款顶级好用的 Vue 表单设计器,其中最后一款卡拉云,是新一代低代码开发工具,不仅能自动生成各类表单,还可以拖拽生成其他常见的前端组件,一行代码连接前后端数据,可快速接入数据库/api。它是表单设计器的超集,可直接生成属于你的后台管理工具,无敌好用。
4204 0
多款顶级好用的 Vue 表单设计器测评推荐,可拖拽生成表单
|
前端开发 JavaScript API
在 Vue3 + Element Plus 中生成动态表格,动态修改表格,多级表头,合并单元格
在 Vue 中,表格组件是使用频率及复杂度排名第一的组件,前端经常需要根据后台返回的数据动态渲染表格,比如动态表格如何生成,因为表格的列并不是固定的,在未知表格具体有哪些列的场景下,前端如何动态渲染表格数据。又或者需要把表格单元格进行合并处理,比如第一列是日期,需要把相同的日期进行合并,这样表格看起来会更加清晰。 本文手把手教你如何在 Vue3 + Element Plus 中创建表格、生成动态表格、创建动态多级表头、表格行合并、列合并等问题。如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云 ,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即
3470 0
|
9月前
|
人工智能 Kubernetes Cloud Native
跨越鸿沟:PAI-DSW 支持动态数据挂载新体验
本文讲述了如何在 PAI-DSW 中集成和利用 Fluid 框架,以及通过动态挂载技术实现 OSS 等存储介质上数据集的快速接入和管理。通过案例演示,进一步展示了动态挂载功能的实际应用效果和优势。
|
资源调度 JavaScript API
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
9563 120
|
10月前
|
安全 Java 测试技术
springboot之SpringBoot单元测试
本文介绍了Spring和Spring Boot项目的单元测试方法,包括使用`@RunWith(SpringJUnit4ClassRunner.class)`、`@WebAppConfiguration`等注解配置测试环境,利用`MockMvc`进行HTTP请求模拟测试,以及如何结合Spring Security进行安全相关的单元测试。Spring Boot中则推荐使用`@SpringBootTest`注解简化测试配置。
382 4