当前位置: 主页 > JAVA语言

java自定义表单引擎-表单引擎 绑定数据字典

发布时间:2023-04-03 16:10   浏览次数:次   作者:佚名

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业务都在重复性建设的背景下,表单核心要实现的目标如下:

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

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

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

整体架构

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

image.png

java自定义表单引擎_开源 表单引擎_表单引擎 绑定数据字典

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

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

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

基础协议

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

开源 表单引擎_java自定义表单引擎_表单引擎 绑定数据字典

{
    "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}}"
    }
}

表单引擎 绑定数据字典_开源 表单引擎_java自定义表单引擎

布局协议

布局协议描述目前仍处于草案阶段,关于布局协议在之前也曾多次讨论到,是否有必要重新定义一套关于布局的协议是比较有争议的点,当然也是我们尽量避免。本身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个:

image.png

image.png

表单引擎 绑定数据字典_开源 表单引擎_java自定义表单引擎

渲染层

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

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

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

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

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

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

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

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

image.png

java自定义表单引擎_表单引擎 绑定数据字典_开源 表单引擎

例子中实际有两种组件java自定义表单引擎,一种是以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在集团内业务的试点落地,同时进行开源运作推广。

官网地址:

Github开源项目地址: