• 试着换个角度理解低代码平台设计的本质


    封面图

    本文会主要分享自己对低代码平台的理解,从多个角度和问题去看低代码平台的设计。我觉得低代码平台的核心在于模型设计,包括控件模型、组件模型、画布模型等等。希望看完本文,你能知道:

    • 低代码平台核心的底层逻辑是什么?
    • 为何常见低代码平台都包含“控件区”、“布局区”和“属性编辑区”?
    • 低代码平台的控件、组件、画布的本质是什么?
    • 如果让低代码平台支持跨平台?
    • 如何让低代码平台支持自定义数据源?

    那让我们开始吧​。

    一、你所看见过的低代码平台

    近几年国内纷纷出现各种低代码产品,在降本增效提质方面发挥重要作用。低代码平台的业务场景涉及越来越广泛:自定义表单、页面制作、活动详情页、工作流场景、数据报表、大屏数据报表、数据表格、白板笔记等等。对应成熟的低代码产品也非常多:阿里宜搭腾讯云搭百度爱速搭轻流Jeecg Boot码良等等。

    下图腾讯开源的 tmagic 平台,是我们最常见的低代码平台布局方式:

    tmagic
    (本图来自: tmagic

    其中包括三个核心模块:

    • 控件区:展示平台内支持的控件,用户通过拖拽控件到布局区,即可展示控件对应的 UI 组件样式;
    • 布局区:用来承载控件对应的 UI 组件,用户可以对每个 UI 组件进行布局,并且直观查看页面效果;
    • 属性编辑区:用来展示该控件支持的配置内容,可以更加灵活的对每个控件对应的 UI 组件进行自定义设置。

    所以,为何各个产品纷纷采用这类布局?

    二、换个角度思考低代码平台设计

    我们在解决问题时,经常会使用两种方法:

    • 自顶向下法:从目标出发,拆解和细化问题,找到解决方法;
    • 自底向上法:汇总各种零散信息,得到正确方法和结论。

    我们试着用自顶向下法思考一下低代码平台的设计:

    通常在团队确定是否需要开发低代码平台前,都会通过头脑风暴、灵感讨论、业务需要情况分析,然后确定开发低代码平台的原始需求。
    7

    假设这么一个场景:

    掘金社区的主页布局比较单一,当需要增加或调整部分模块时,需要改动项目代码、打包、提测、发布,这时候如果能有一个主页设计平台,让运营人员自由调整页面布局,还可以针对不同节日、活动调整出不同主页布局。
    juejin

    基于这样的场景,我们使用自顶向下法,从目标出发,拆解和细化问题,找出解决方法。

    1. 确定目标

    我们的目标需求是能够灵活的布局社区主页:
    8

    2. 拆解和细化问题

    如果要实现灵活布局的掘金主页,就需要将主页中的模块抽成每个独立控件:
    9
    如果每个控件需要能够灵活配置,我们还需要能够配置控件的任意部分:
    10

    3. 找到解决方法

    按照前两个步骤的分析,我们可以确定大致解决方法:

    1. 需要实现一个支持自由拖拽布局的设计平台;
    2. 该平台支持拖拽不同控件到页面中;
    3. 每个控件支持不同的自定义配置;
    4. 设计器支持导出页面结构,渲染器支持渲染页面内容。

    于是我们就有了下面的方案:
    1
    这样是为什么常见低代码平台都会有“控件区”、“布局区”和“属性编辑区”。

    通常交互逻辑如下:

    1. 从「控件区」拖拽一个控件进入「布局区」,将控件渲染成对应组件;
    2. 选中组件,在「属性配置区」显示该组件所有支持配置的属性;
    3. 修改「属性配置区」的属性,更新「布局区」中该组件的样式。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XinjYk6-1658274042982)(https://images.pingan8787.com/lowcode/20220719/2.png)]
    这是最简单的一个流程。

    三、思考更加通用的低代码模型

    低代码平台创建的页面,本质上不一定是单个页面,也可以是由多个页面组成的一个 Web 应用,因此,我们可以把上面示例,抽象成更加通用的低代码平台模型:
    3
    该模型定义了低代码平台创建的页面结构,最终的渲染是由对应渲染器渲染页面。

    这就有点 VNode 树的味道啦。

    vnode
    (图片来源:https://v3.cn.vuejs.org/

    对于 Vue 而言,核心要解决的就是“如何创建 VNode”和“如何渲染 VNode”。

    接下来我们通过 TypeScript 接口形式定义下面的结构:

    4
    可以发现,单页应用和多页应用的关系在于,通过为单页应用增加 path配置,将多个单页应用组合成多页应用。

    到这里我们就有一个更加通用的低代码模型,并且使用 TypeScript 接口定义了每一层的结构。

    可以看出:低代码平台的核心在于模型设计,定义每个部分的模型。

    四、控件区的控件没这么简单

    1. 控件是什么?

    控件本质是一个标准的 JSONSchema 对象,用来描述最终渲染出来的组件。在低代码平台中,将控件拖拽到布局区才会显示对应的组件样式。

    以「用户信息控件」为例:

    const UserInfo = {
        name: '用户信息控件',
        type: 'UserInfoComponent', // 指定渲染的组件名称
        config: [
            {
                label: '头像',
                type: 'input',
                value: 'https://a.com',
            },
            {
                label: '昵称',
                type: 'input',
                value: 'pingan8787'
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    通常我们会在控件对象中定义一个 type(也可能是其他名称),用来指定控件所渲染的组件名称。比如 Vue 中,就可以通过该 type 值,使用动态组件 形式动态渲染组件。
    5
    控件就好比是组件的说明书,只是对组件进行描述,描述了它是什么样子,有哪些行为、配置等信息。

    2. 控件还有什么优点?

    控件定义成标准的 JSON 对象,还有其他优点没比如:可以实现控件跨平台适配,在不同平台/组件库渲染不同的组件。目标平台只需按照模型渲染不同组件即可。
    6

    3. 控件如何实现动态加载远程组件?

    常见的方案是为每个控件指定远程组件的地址(如设置 path 属性),当控件开始被拖拽时,发送请求获取远程组件:

    const UserInfo = {
        name: '用户信息控件',
        type: 'UserInfoComponent', // 指定渲染的组件名称
        path: 'https://a.com/UserInfoComponent.js', // 远程组件的地址
        config: [
            {
                label: '头像',
                type: 'input',
                value: 'https://a.com',
            },
            {
                label: '昵称',
                type: 'input',
                value: 'pingan8787'
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    以 Vue 为例,当获取到远程 Vue 组件后,可以通过 Vue 提供的动态组件进行注册和使用。
    13
    完整过程如下:

    1. 开始拖拽「控件区」控件,并发起请求,从服务端获取远程组件;
    2. 当获取到远程组件后,注册到项目中;
    3. 松开控件,渲染组件内容到「画布区」。

    当然,考虑到编辑器的性能优化,避免每次拖拽都发送请求获取组件文件,我们可以这样优化:

    • 使用请求缓存,如果是重复请求,则从缓存读取上次请求结果;
    • 对常用基础组件预先发送请求并保存本地;
    • 本地缓存已请求的组件,下次请求相同组件,则读取缓存结果;
    • 等等

    五、画布区的画布也没这么简单

    1. 画布是什么?

    画布的本质也是一个**标准 JSON 对象,**它是我们最终要渲染页面所用的数据源,通常包含整个页面的结构和配置信息。当拖拽控件进入画布和更新组件配置时,会更新画布。

    我们根据掘金主页,简单构造一个模型(不考虑多页面情况):

    const Juejin = {
        title: '掘金主页',
        favicon: './favicon.icon',
        components: [
            {
                name: '用户信息控件',
                type: 'UserInfoComponent',
                config: [
                    {
                        label: '头像',
                        type: 'input',
                        value: 'https://a.com',
                    },
                    {
                        label: '昵称',
                        type: 'input',
                        value: 'pingan8787'
                    }
                ]
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在上面模型中,定义了画布中的每个组件,存放在 components数组下,每个组件都包含各自的 nametypeconfig等信息,在渲染器渲染时,就可以:

    • 根据 type渲染配置区的组件;
    • 根据 label 渲染配置区表单的 label 文本;
    • 根据 value渲染配置区表单的值。

    2. 画布还有丰富的配置

    对于画布模型,最重要的应该是组件列表,即前面的 components数组,对于每一个组件,最主要的信息包括:

    • 事件模型信息:包含该组件绑定的一些事件(如事件名称等);
    • 动画模型信息:包含该组件绑定的一些动画效果(如旋转、放大等);
    • UI 样式模型信息:包含该组件绑定的一些 UI 样式(如背景色、字号等);
    • 数据/数据源模型信息:包含该组件绑定的一些数据源相关的配置(如数据源接口地址等)。

    以「事件模型信息」为例,当页面中配置了一个按钮,这个按钮往往可以做如下事情:

    • 打开链接;
    • 打开弹框;
    • 打开 APP;
    • 刷新页面;
    • 发送请求;
    • 等等。

    此时,该按钮可触发的行为非常多,如果把每个事件处理逻辑都写在组件中,会使得组件臃肿无比,且耦合在组件中,可维护性差。

    为了降低组件和事件处理逻辑之间的耦合度,我们可以在组件和事件处理逻辑中间增加一层,即事件总线:

    14
    实现通用组件派发事件到事件总线,不同的业务场景监听事件,执行具体的事件处理逻辑。

    通过事件总线,将派发事件和监听事件的双方互相解耦,完成解耦后,还能够实现跨平台的功能,对于派发相同的事件,只需要在不同平台监听该事件,实现不同的处理逻辑即可

    六、数据源设计

    所谓「数据源」即低代码平台中数据来源,通常按照业务需求可以将数据源分为两类:

    • 静态数据源:数据绑定在页面配置中,在最终效果页时,直接使用页面配置中的数据,无需通过接口获取数据;
    • 动态数据源:一般是保存数据源的接口在配置中,不绑定数据,在最终效果页时,客户端需要再发送请求获取数据。

    1. 静态数据源的过程

    在低代码设计平台中,平台先请求数据,用户选择其中指定数据,保存在页面配置中。

    比如当我们已有 banner 列表接口,需要选择其中一张,添加到布局区中:

    15
    步骤如下:

    1. 用户在「控件区」选择「轮播控件」,拖入「布局区」;
    2. 点击「布局区」中「轮播控件」的组件,打开「属性配置区」;
    3. 选择「属性配置区」中「选择 banner」,平台发送请求,从服务端获取 banner 列表;
    4. 打开「选择 banner 弹框」,展示 banner 列表,用户选择所需 banner 图片;
    5. 点击「确定」,关闭「选择 banner 」弹框,并在「布局区」的「轮播控件」组件插入该笔数据,完成选择。

    用户在「选择 banner」弹框中,选中指定的数据,保存到页面配置中,当访问最终生成效果页,会直接显示出已选择的 banner 图片。

    2. 动态数据源的过程

    动态数据源相比静态数据源,会更加灵活,用户指定数据源接口后,当接口数据变化,最终效果页可以动态改变展示的内容。

    比如当我们已有 banner 列表接口,可以在管理后台添加不同的 banner,最终效果页能够展示新的 banner,而用户只需在设计时,指定 banner 列表接口即可:
    16
    步骤如下:

    1. 用户在「控件区」选择「轮播控件」,拖入「布局区」;
    2. 点击「布局区」中「轮播控件」的组件,打开「属性配置区」;
    3. 选择「属性配置区」中「配置 banner」,配置“接口地址”和“转换规则”;
    4. 选择完成,点击「确定」,关闭「选择 banner 」弹框,将配置的“接口地址”和“转换规则”数据保存在「布局区」页面配置中,配置完成。
    5. 当用户访问最终效果页时,页面会先调用配置的“接口地址”获取远程的 banner 列表;
    6. 将接口返回的数据通过“转换规则”,将接口返回的数据转换成组件所有的数据格式。

    这样就实现了最终效果页能够每次都展示最新的数据,实现完全动态。

    3. 增加数据源适配器

    当需要对两个耦合度较高的逻辑进行解耦,可以通过增加适配器方法进行解耦,因此在数据源这边也可以增加适配器对「UI 组件」和「接口数据」进行解耦。

    理想状态应该是:

    • UI 组件只对外暴露组件支持的配置和方法,而无需关注是什么业务使用该组件;
    • 接口数据也无需关注数据被什么组件使用。

    于是,我们分别为「静态数据源」和「动态数据源」增加了数据适配器,流程如下:

    • 静态数据源

    11
    在第 4 步时,接口返回的数据会经过「数据适配器 1」,将接口数据转换为「选择 banner」弹框组件统一的参数。同理,第 6 步将弹框组件返回的数据结构,通过「数据适配器2」转换为「banner 组件」所需参数的数据结构。

    • 动态数据源

    12
    在第 6 步时,接口返回的数据会经过「数据适配器 」,将接口数据转换为「banner 组件」统一的参数数据结构。

    其实总结一下,就是通过各种数据适配器,将各种来源的数据结构转换为组件的参数模型即可。好处也很明显:

    • 更换数据源时,只需要按照组件参数模型对接接口,实现各种数据适配器,无需改动原有逻辑;
    • 更换 UI 组件库时,也只需要按照组件参数模型对接 UI 组件,实现各种数据适配器,无需改动原有逻辑。

    4. 总结数据源设计

    按照前面的方案,我们对数据源就有了主要方向,其主要的核心在于:通过定义组件接口模型和适配器模型,我们可以很容易的开发任意组件和适配器,按照定义的模型,其他开发者也能很方便的开发。

    七、总结

    低代码平台作用在于降本增效提质,核心在于模型设计,降低各个功能点的耦合度,让平台支持跨平台

    本文通过自顶向下法,介绍低代码平台的设计思路,从目标出发,拆解和细化问题,找到解决方法。后面针对低代码平台的几个核心模块逐一分析自己的理解,着重介绍了核心模块的模型设计和配置。

    本文是自己经过几个低代码平台实战后的理解和总结,希望对各位有所帮助,低代码平台的未来无限可能。

    这是我第一次写低代码相关的文章,如有错误,欢迎指正~~

  • 相关阅读:
    c++作业
    程序员股东转让出资合同书
    算法链表-局部反转
    什么是机器学习中的目标函数和优化算法,列举几种常见的优化算法
    ESP8266-Arduino编程实例-MMA7455L加速计驱动
    C#在winform 控制台输出 cmd窗口
    Docker 工作原理分析
    路由之间的转跳及传参
    mmrotate自定义数据集安装部署训练测试
    Linux新的IO模型io_uring
  • 原文地址:https://blog.csdn.net/qq_36380426/article/details/125884468