Time Bomb Veela
  1. 1 Time Bomb Veela
  2. 2 BREAK IN TO BREAK OUT Lyn
  3. 3 Warcry mpi
  4. 4 Flower Of Life 发热巫女
  5. 5 Last Surprise Lyn
  6. 6 Libertus Chen-U
  7. 7 かかってこいよ NakamuraEmi
  8. 8 Life Will Change Lyn
  9. 9 Hypocrite Nush
  10. 10 One Last You Jen Bird
2019-12-15 16:35:29

《低代码平台》分享文字稿(下)

下篇谈平台设计大概需要注意哪些东西。

第十一页——结构图

整个体系的概览是这样,首先最基础的部分称作组件物料,它是在普通的组件之上封装了通用业务逻辑和数据管理逻辑而成的,也是平台和外界通信的基础部件,可以说是一个低代码平台的原子单位,JSON中的每一个type就对应一个对应的组件物料。平台的中期产出物就是JSON,需要我们开发编写的部分也就到JSON为止,除了手写外,也可以借助各种工具,比如cli或者IDE来帮助生成JSON。最终JSON会通过一个运行时框架递归解析成最终的页面代码。

可以看到整个体系需要产出的3个部分分别是,组件物料,运行时框架和IDE。运行时框架依赖物料,IDE依赖运行时框架。因为是面向开发者,需要提供渐进式的开发体验。希望只要了解最基本的功能就可以上手,当要处理更高级的需求时才需要学习高级功能,所以这3者也可以分离使用,这就是我们最终的目的。接下来我们一步一步看设计上需要注意的点。

第十二页——组件物料

首先我们考虑将组件物料分成两个部分,通用部分不涉及特殊的UI交互组件,主要处理通用样式和通用业务逻辑,比如说Grid这样的布局组件,Curd这样的处理数据的组件,这一套由平台内部提供。另外一部分叫UI物料,需要做成插件式的,不和平台体系耦合,同时可以脱离平台独立使用。这样有什么好处,比如说我们大多数后台项目是用eleme搭的,那么平台本身可能会以eleme为默认的组件物料适配大多数后台项目。但有的后台项目是自研的一套组件库,但又想用到这个低代码平台的能力,那么它就完全可以利用这个插件机制自己维护一套基于自己组件库的组件物料给自己用,不需要耗费平台开发者的精力,当然这需要平台提供自定义接入的能力,在后面讲。

基于组件库封装的组件物料其实还是组件,只不过是可以被JSON描述被运行时框架解析的组件。那么需要考虑的就这几点:

  1. 屏蔽下层组件的函数接口,这很好理解,首先JSON不支持函数,同时我们也要控制模板复杂度,保证模板里出现的东西都是框架解析时理解的,不能出现用户自定义的部分。
  2. 封装更适用于配置的接口和通用逻辑,这就是把重复模式封装的部分了。比如表单验证,数据获取这种都是在组件物料中处理的,并提供一些props给用户控制。
  3. 一个组件是不可能完成整个逻辑闭环的,简单来说,数据获取,你总要找个地方存,所以需要用到运行时框架提供的能力,那么组件物料里就需要有和运行时框架连接的胶水代码。

下面就可以看运行时框架的设计了。

第十三页——运行时框架

首先设计上要做到独立使用,即使是脱离了IDE,在需要的地方就可以即插即用,类似于Vue从一个选项对象就可以创建组件,运行时框架也要做到从一段JSON就能创建组件的能力。那么本质其实也就是一个从DSL到组件视图的映射函数了。根据JSON结构可以自由控制粒度,小到一个小组件嵌入任意页面,大到整个页面都能用这个运行时框架满足要求。

框架的运行流程如图。因为留给用户控制的只有DSL的props,就像KTV的那种花里胡哨的灯的开关一样,按什么开关就有什么效果,其实就是设计模式里的状态模式了,由用户通过DSL props去区分组件物料的状态,进而将状态委托到组件物料内部驱动组件行为的改变,产生结果专有的数据源,再映射回组件决定最终的视图。举个例子,比如表单验证逻辑,我们自定义一个validation的字段,然后DSL通过validations传递到组件物料,组件物料根据validations的不同走不同的验证逻辑,最终产生专属状态数据决定组件如何渲染,比如validations可能有state和error message。基本上所有的业务都可以用这种形式来描述,props决定行为,行为产生数据,数据决定视图。

决定思路后其实就有两个核心问题要解决,一个就是数据管理,通过DSL props产生的状态数据如何合理的管理,然后就是交互逻辑代码涉及到其他组件时要怎么通信。比如点击一个按钮组件,要驱动另一个表单组件的做提交。

第十四页——组件数据管理

当然其实数据管理不止是组件状态的数据管理,组件本身的管理也要说一下,比较直观的想法就是管理一个组件池,递归解析DSL的过程根据一个标识字段比如type从池子里捞出来渲染。这样是没问题,但有一些细节需要注意,一个是怎么暴露出自定义接口,暴露出自定义接口后当用户注册进来时名称冲突的权重设计。还有就是本身命名相同的组件在不同上下文中表现也不相同,比如switch,在表单中切换时需要改变表单值,在表格中可能就是控制一次ajax的提交,这种情况怎么处理。我这里说一个常规方案的话,就是匹配的时候不要用标识符,而是用标识符形成的路径,就记录了除了组件本身以外的上下文信息。

第十五页——业务数据获取

然后是业务数据管理,分两块来看,首先是业务数据的获取。运行时框架可以看做一个黑盒,首先是把JSON丢进去得到视图。然后这个视图怎么处理数据,用户是不用了解的,用户只需要按照接口规范提供数据就行。那么一个很常见的问题就是中后台的数据来源于各种服务,数据结构很难在后端上约定统一,如果有BFF层在BFF层处理最合适,没有的话框架设计上就要提供这样一种规范化数据的服务,在进入到最终渲染的组件物料之前所有的请求都要在这里做一次适配器处理。比如说我们可以设计一个特殊的API对象,这个API对象可以自定义适配逻辑,它可以配置在需要数据交互的组件中,那么运行时框架解析到这个API时就可以先处理一次再丢进最终的组件里。

第十六页——业务数据单存储

再谈业务数据的存储。最直观的想法是目前前端常规应用最广泛的单Store状态管理机制,状态统一管理再下发到各个组件。但这样有个问题就是,两个特定的组件通信时的数据传递会有麻烦。比如A组件需要拿到B组件中的数据时,就需要通过一个特殊的key显式声明去解决,但这样又会带来新的问题:模板复杂度会提高,JSON中的各个组件依赖会呈网状,并且在配置上也会耦合起来,还有可能有循环依赖的问题,同时还要考虑key冲突时的处理。如果能合适的处理的话,单存储的组件平级和父子级通信也就简单了。

第十七页——业务数据多存储

另外一种思路是利用组件树的天然结构,让需要状态的组件按需声明自己的专属store,全局store只用来管理专属store的创建存储的销毁,不存储具体数据。并跟组件树层次保持一致下发,形成作用域链的模式,然后每个组件都可以取到自己上层的所有store中的数据。这样的好处首先就是降低了模板复杂度,相当于隐式的制定一条规则,所有组件天然能获取到上层的数据。这样就相当于所有组件都有一个默认的数据来源,不需要再声明到模板中。并且90%的情况下,组件间的状态共享都来自于上下级,不需要任何额外声明就能满足常规的组件通信。当然缺点就是不能实现平级的组件通信,需要用另外的手段。

把这个组件物料注册进框架的时候增加一个 store声明,框架就会用高阶组件的形式把带store的组件扔进组件池。

第十八页——组件通信

了解了组件各自的数据存储之后,就可以谈具体的组件通信模式了。常规来说是事件+自定义函数的形式,但是要注意在配置中引入函数就是灵活度爆炸和复杂度爆炸的开始,一个低代码平台要不要暴露出函数钩子,还是要看具体场景。暴露出自由定义逻辑的接口,虽然能实现的需求增加,但是可维护性和易用性也会同时降低。约束代表着易用,通过对逻辑使用的限制,我们能保证对整个应用的百分百控制,项目里做了什么,都是框架理解的,也就意味着使用者配置好业务后,后续的维护工作都可以由框架接手。如果引入自定义函数,这部分函数的维护就是个问题,对于框架也是未知且最容易出问题的东西。所以我认为中后台的场景下,没有这个必要,我们可以考虑在事件和函数之上抽象出一种新的通信手段,比如叫Action,Action的类型是可枚举的并且由框架本身决定,组件通信就可以用 action作为媒介。

上下级通信就是,作用域链获取上层数据,当然在JSON中不能用变量,要实现一种特定的解析语法处理数据。在能够响应action的组件中约定onAction这样一种方法,各种组件实现自己的onAction来响应下层触发的动作。

第十九页——同级通信

同级通信的话,就还是以Action为媒介,在一些特殊的组件上实现scope这样一种功能提供桥梁,比如form,table这样可以预见到的平级处理较多的容器型组件,仍然采用高阶组件的形式。下层组件在创建时都可以注册到这个scope上来,通过target和name标识,把动作信息传递到scope,再由scope在注册到自己上面的组件上找到对应name的组件传递过去。当然这样同样会有name冲突的问题,但实际上平级通信的场景很少,大多数情况下都是上下级通信,然后在同一容器下name相同的场景就更少了,这种情况可以忽略不计,就算有也可以起个不同的标识名绕过去,所以还是一个比较合理的方案。

第二十页——IDE

至此我们已经有了组件物料和运行时框架,已经可以实现从JSON到组件的过程了。最后就是IDE的开发,进一步提升开发效率和降低开发门槛。因为IDE开发又是个很大的话题这里时间原因就不谈太多了,就说一下整个架构不可缺少的核心和实现上要注意的几个关键点。

首先明确IDE的定位是结构组装流水线,内容部分应该由运行时框架和组件物料管理。那么第一步要做到的就是内容和结构分离。具体上来说,组件编辑状态的渲染内容应该由组件本身提供,IDE只负责传入一个目前是否是编辑状态的参数,然后由运行时框架决定如何渲染。这样的好处就是IDE关注的信息变少,所有的组件都可以当做一个通用节点,功能的实现都是基于这个通用节点上。

再就是通用视图组件的实现,这个用来响应各种事件,比如拖曳的可视化编排,右键调用控制面板,左键在编辑器中编辑属性。它也不用知道内部的组件是什么,只要知道一个ID,ID对应着哪段JSON结构以及该结构的父子关系就行。这个视图组件还可以设置到编辑态渲染的内部作为视图注入点,比如一个page组件可能有head,body,footer三个部分,那么page部分渲染编辑态的时候,这三个部分就是3个视图组件,至于渲染什么内容page组件完全不用管。

再就是入参定制的流程,其实也就是基于ID了。关键就在于点击一个组件时如何准确找到JSON中对应的那个区块。首先就是在这个JSON进入到渲染器之前,就由IDE递归一遍,并给每个对象设置ID,然后进入渲染器渲染时记录ID,再点击时根据这个ID去原JSON中找到这个区块丢到编辑器里编辑,再合并回JSON。流程大概是这样但也有很多细节要注意的,比如有些场景递归查找可能有性能问题,也有把JSON拆分为二维表再合并的方案,再有的JSON字段中,渲染器可以设置为字符串,ID加不上该怎么处理,都可以思考一下。

第二十一页——命令系统

最后再讲一下命令系统,因为IDE的特性,很多相同的功能分布在各个模块中。比如模块A是节点编辑功能,可以用快捷键,也有菜单控制和右击控制面板控制,也可以从模块B中调用,模块B也可以用菜单,控制面板,快捷键调用,这样就会造成依赖复杂难以理清的问题。一般都会实现一个中心化的命令系统,其实就是设计模式里的命令模式了,把调用请求封装成对象传到命令系统中,再让命令系统找到对应的模块去调用。

第二十二页——Saya

因为是实例展示,文字稿就没什么好说的了。不过这里可以给看我博客的同学一个小礼物,也是我在分享会上没有说到的内容——Saya的意义。

neta自沙耶之歌。这个游戏应该还算是比较有名?还是简单介绍一下。讲的是主人公因为遭遇事故,认知系统出现了问题——现实中的人在他眼中全部成了怪物。直到有一天,在这个充满怪物的世界里,他遇到了一个美丽的女孩并和她相恋的故事。

那么在他认知中的女孩,在现实中是怎样的存在就不用我多说了吧。这个游戏抛出了一个问题并探讨——在爱情中,精神和肉体,哪一份占比更多?回到低代码平台来看,我的寓意是,在代码的编写中,需要被封装的部分和需要被暴露的部分的界限在哪里?这也是低代码平台需要关注的一个核心设计点。

好吧(笑),总之不管你接不接受这个寓意,终究是我无聊的个人想法而已。算是我的一个坏习惯?完成一份作品的同时,总会花不少时间给它起一个奇怪的名字,但也是我非常享受的,有仪式感的时刻。因为有了你的思想,所以这个作品才是独一无二的,由你创造的东西,是你和这个世界连接的证明,怎么能不重视呢。

小结

倒没什么别的说的了,就是近两周都没怎么学别的东西,有点惭愧,计划落后了,赶紧在过年前花时间补上来。哦对了,今天在KFC遇到了一位超漂亮的短发女生在办公,没好意思多看两眼,取完餐就走了。回来的路上,等红绿灯的时候,夕阳洒在对面的铁轨上,我看得出神。不经意侧过脸,那位女生不知何时站到了我旁边,看来是办完公准备回家了。

嗯,还是很美的,无论是夕阳下的铁轨,短发女生,还是这个世界。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「工程化」「Vue」「设计模式」 标签。