![](/assets/img/weixin-blog.311ae013.png)
# 前端工程化提效新思路
目录
前言
目标
分析
方案设计——页面如何可视化及代码生成
问题分析
提出想法
方案设计
方案验证
方案设计——组件间/页面间数据通信
问题分析
提出想法
方案设计
方案验证
总结
# 前言
什么是 Mis 系统?
Mis 全称为 Management Information System; 由人、计算机及其他外围设备等组成的能进行信息的收集、传递、存贮、加工、维护和使用的系统,通常是对信息实现权限管理/增删改查。
为什么要进行 Mis 系统开发提效?
随着团队业务的增长,合规 及 团队工程化带来的管理诉求加大,需要大量的 Mis 系统进行支撑。
现在的研发流程是怎样的?
在日常的 Mis 系统开发过程中,前端同学在接到业务需求到需求上线时大多需要进行如下几个步骤(以 vue 框架为例):
需求分析 -> 项目创建 -> 环境配置 -> 已有 ui 库引入 -> 代码编写 -> 联调测试 -> 打包 -> 发布部署 -> 监控
enter image description here
其中 项目创建/环境配置/项目打包/发布:可以通过团队脚手架将能力内置/统一;无需手动处理;前端同学更多需要关心的部分是: 契约的确定,前端代码的编写。
能够快速生成前端页面市面上有很多解决方案:
- 低代码工具快速建站: 通过在线/GUI 工具让用户进行页面配置,系统自动生成页面; 代表为 mendix/salesforce/微搭/无极等
优点: 门槛低,建站快;
缺点: . 低代码平台能够解决 80%较为简单的场景,剩余的 20%较为复杂的场景,需要开发者使用平台提供的 api 进行功能扩展, . 不直接生成前端代码,要进行功能扩展需要熟悉平台的一系列规则,造成前端不想用后端不会用的尴尬局面; 若后续低代码平台不维护则又要面临项目重构。
- 基于场景的模块化定制: 针对特定场景,部分同学封装通用组件,开放出配置项,其余同学引入组件进行配置,可达到快速产生页面效果的目的。代表有: 明道云
优点: . 部分前端同学无需关心组件如何实现,直接使用对应的配置,即可快速完成部分需要的开发;
缺点: . 适用于部分业务场景,组件臃肿庞大 . 通用性和灵活性很难权衡 . 开发需要对场景特别了解
- 基于纯 DSL 进行配置: 通过 JSON 配置就能生成各种页面。代表为: 百度 amis
优点: 可通过 json/dsl 快速搭建出页面,不需要懂前端,不收前端技术影响
缺点: . 需要学习另外一套语法,有成本 . 灵活性相对不足
# 目标
所以,有没有一种可能: 与传统前端开发无异的,不追求完全线上化操作的,快速生成可二次编辑代码的,工程化工具,来进行开发提效;
与传统开发无异: 前端同学无需学习额外的语法,通过工具快速生成前端页面的代码(页面代码,属性配置,路由配置,vuex 编写,监控接入等);
不完全线上化操作: 对于常规的操作(项目创建,环境配置,打包,发布,及组件支持的属性配置等)可以通过可视化的方式进行配置,对于工具不满足的情况,可以拉下代码自己开发;
可二次开发:前端无额外的学习成本,即可快速对生成的代码进行二次修改(如果功能不满足的情况下)
目标群体及使用方式(基于已创建项目的前提下):
For 前端同学: 可以使用工具快速生成前端代码进行应用开发,对于简单的场景甚至不需要二次开发;
For 后端同学: 对于 Mis 系统而言,大多是表单,表格及图表类展示,后端同学可以使用前端同学提供的组件通过可视化的方式快速搭建页面,对于较为复杂的场景可以扩展组件或者二次开发;
# 分析
围绕着上面的目标,建设一个如上的工程化工具,主要需要解决 2 个问题:
- 前端同学常写的代码由哪些部分组成?
除开项目创建,环境配置,打包发布,等;仅分析前端编写代码部分;我们发现主要由四部分组成:
数据源: 针对 Mis 系统,没有数据的页面是没有灵魂的,数据通常包括:http 数据,url 参数,localstorage,cookie,等;
页面: 编写 vue 组件,页面用于展示内容,通常我们基于 vue/react/angular 等框架并结合 UI 组件库(tdesign,element-ui,ant-design,iview)快速构建出 UI;
通信: 页面/组件之间可能需要进行数据通信;比如上面通过输入框输入用户名下面需要实时展示出来;
事件: 事件是我们在编程时系统内发生的动作或者发生的事情; 比如点击页面上的按钮弹窗展示
- 如何抽象代码部分,并通过可视化的方式快速生成代码?
如何对数据源,页面,通信及事件,进行抽象,达到既能通过可视化的方式创建页面,管理数据,管理事件,又能直接转换落地到真实的代码的目的;
由于篇幅有限本文将着重先讲:
页面如何可视化及代码生成
如何进行组件间通信
数据源及事件部分可能留在下篇文章进行阐述。
# 方案设计——页面如何可视化及代码生成
# 问题分析
由于我们这边是 Vue 技术栈,本文暂以 Vue2.x 为例讲述如何进行代码生成及可视化操作;
要生成 Vue 代码,首先的明白 Vue 代码模板转换到视图的过程及代码有那几部分组成:
我们的主要目的是生成 template 部分代码,所以可以尝试着反向查阅 template 编译成 render 函数的过程,代码主要有那几部分组成;方便代码即可转换成 template 又可转换成 render 函数(根据用户需求)。从渲染函数中我们发现代码主要需要三个部分:
标签名 :声明使用什么类型的组件
属性:组件属性的配置,属性分为静态属性及动态属性
子节点:子项中分为 children 及 slot,children 其实是 slot default,所以我们可以将 children 归并为 slot
# 提出想法
由此我们尝试设计一种 schema 去描述页面:
export interface PageUISchemaType { // ... // 组件类型,比如使用t-input组件那么type就为t-input type: string; // 版本 version?: string; // 组件属性 props?: SchemaBasePropType; // vue插槽部分 slots?: { [key: string]: PageUISchemaType[], }; // ... }
type : 组件类型,比如我们使用了 tdesign 中的 t-input 组件,那么 type 就为 t-input,渲染到 vue template 中也就是
<t-input></t-input>
props: 属性分为静态属性和动态属性
静态属性: 可直接传入对应的值: 比如下面的
userName
为alex
;动态属性: 由于数据存在动态情况,此处的
$page.data
相当于一个简写获取当前页面组件中的data/computed
的值;$global.data
相当于获取 vuex 中的值;
{ "props": { "userName": "alex", ":age": "$page.data.age" } }
- slots: vue 插槽分为默认插槽,具名插槽及作用域插槽,我们主要采用了具名插槽的方式,融合了默认插槽。
{ // key表示查查名称,插槽默认可层级嵌套多个组件 [key: string]: PageUISchemaType[], }
使用如上的 schema 描述,是否能够实现编辑器在线编辑及代码生成?
# 方案设计
首先我们来明确一下可视化编辑器及代码生成的关系: 理论上来讲:
可视化编辑器创造 schema 同时又使用 schema 进行在线预览
本地代码可以通过 schema 转换而成,又可以转换成 schema
但是事实上,考虑到本地编写代码灵活性过高,造成可控性较低的原因, /暂时不考虑代码反解析成 schema ;
# 可视化如何构造 schema 并渲染
1. 新增 schema:其大致结构如下:
编辑器中组件新增的来源有三种:
布局层下新增组件,可以使用左侧拖动(draggable)的方式注入组件;
如果存在接口(数据源)可以自动填充对应组件的插槽内容;
对于可自定义插槽部分,可以选择要填充的组件
tips: 为什么没有设计所有的组件都支持拖动方式,主要考虑因素为过于灵活的拖动会造成页面不可控
2. 删除 schema 及修改 schema: 如上图所示,对于组件外层存在一个虚拟组件,当点击选择对应组件时可以进行事件拦截,获取到选中组件的 id 或者路径,获取到路径之后可以直接删除 schema 对象;其次对于组件 schema 的修改,比如属性配置同样是基于选中某个组件的情况下,持有当前 schema 的引用,然后对属性进行修改。
3 编辑器渲染 schema:
下面我们来聊聊可视化编辑器如何利用 schema 进行渲染:
数据准备阶段:当编辑器中新增了未被注册的组件,需要先进行全局注册
渲染阶段:其核心主要利用 vue 自带的动态渲染能力
<component is="" />
,将配置的属性进行进入;如果存在子组件则声明插槽并循环渲染
可视化编辑的目的是构造 schema,对于用户的交互是需要依赖于生成的代码,所以在可视化编辑阶段将事件进行拦截,暂不提供交互;如果需要预览可以切换到预览模式。
# schema 转换成 vue 代码
在转 vue 代码前,我们先思考一下如果写一个页面,常常需要编写那几部分内容:
新建一个 vue 文件,编写 template,script 及 style 部分;处理组件静态属性配置,处理组件动态属性配置
编写路由,将上述创建的文件传递给 vue-router
如果存在跨组件/跨页面通信的情况可能会编写 vuex,
本篇暂不讲述数据源及事件部分代码编写
回到上面的 schema 描述
export interface PageUISchemaType { // ... // 组件类型,比如使用t-input组件那么type就为t-input type: string; // 版本 version?: string; // 组件属性 props?: SchemaBasePropType; // vue插槽部分 slots?: { [key: string]: PageUISchemaType[], }; // ... }
我们的方案是:
主要设计四大部分:
生成前代码准备:注册所需组件
生成 vue 页面代码,包括 tempale 及 script 代码部分;
生成页面路径:通过解析路由文件得到 ast 并转换注入对应的页面路径
生成 vuex 代码:如果存在跨组件或者页面通信的话,会创建对应的 module 并生成 typescript 的 Interface/state/mutation 等;其次会将 module 注入到 vuex 入口文件中
# 方案验证
方案设计部分讲到了 可视化编辑器渲染 schema 及 vue 代码生成:
路由及 vuex 部分代码如下:
# 方案设计——组件间/页面间数据通信
# 问题分析
在前端开发中,大多数情况下组件是不太需要和其他组件进行通信的,组件通信一般应用在:
页面需要进行数据联动: 比如实时再另外部分展示用户输入内容
需要进行层级数据传递
# 提出想法
在 vue 中进行数据通信的方式有很多,比如:
provide/inject
props/emit
vuex
eventbus
attrs/listeners
等
从独立性,简易性,可维护性,可扩展性及功能满足程度五个方面,对比后我们发现
基于props进行父子间数据通信是最有效的方法
vuex 较为适合跨组件进行通信及全局状态管理
# 方案设计
思路:利用 props 实现组件间通信,使用 vuex 实现跨组件/页面间通信
enter image description here
/挑战 1:组件如何进行 props 动态数据绑定?/
前面已经讲过,对于动态的属性我们采用 : 号开头区分是否动态属性
enter image description here
/挑战 2:拦截组件的 set,需要对组件进行递归处理 Proxy 且具有响应式,对前端而言为一个黑盒;/ (与传统开发具有一定差异)
/挑战 3:vuex 如何与页面进行绑定(上述的$page.data 如何转化成真实可执行的代码)?/ 想法: 实现一个类似于 vuex mapState 的功能
动态数据的获取:利用计算属性绑定并监听 state 的值
动态数据的修改:通过提交 commit 给 mutation;通过这种方式与传统开发 获取 state 值,修改 state 值无异,前端同学理解成本较低(解决了挑战 2)
方案完善基于上面的实现思路重新整理了方案:
# 方案验证
我们还是基于上一部分生成的页面代码进行验证: 内容输入:
vuex 数据更新:
# 总结
进行前端开发页面提效的方式有很多种:
低代码
基于模块化的配置开发
基于纯 DSL 开发
代码快速生成
招聘 10+同学
等等
不同的业务场景,不同的业务需求,不管是哪一种方式能够再实际工作中帮助大家提效就是相对最有效的方法。 本文主要基于 代码生成的方式 进行提高效能;当然也不能 100%的覆盖所有的场景,因为场景一直是变化的。但个人理解,基于代码快速生成有如下几个优点:
学习成本低,无需掌握额外的技能知识(比如平台的 api 等);
可扩展性强,因为基于代码生成,对于不满足需求的,前端同学可以进行二次开发;
上手快,通过提供可视化的方式(可视化只是一种选择)抹平技术差异,让前后端同学都可以快速搭建页面;对于不能满足业务场景的需求,可以扩展基础组件或者基于已生成的代码进行扩展;
可维护性高,整个项目的代码是可见的,和部分低代码平台代码封闭的区别在于,较少的使用工具的 api,尽可能将业务代码还给业务;假若有一天工具不维护,对于项目而言和传统需求开发无异,无需重构/迁移;
效能相对较高,对于任何一个工具/平台而言,能否快速搭建出页面,其中一个关键点在于页面的构成元素(比如: 表格,表单,图表)的功能支持 是否相对丰富;
个人理解: 对于 Mis 场景的页面应该是有那些功能相对丰富的元素/模块进行组合而成,而不是过于灵活的从 0 开始搭建(比如:一个页面直接拖入一个表格/表单 会比用户逐个拖入输入框来的简单高效) ;其次对于固化的场景快速填充内容也是不错的选择(比如:后端同学有一个表单提交的需求,他已有了一个接口,比较好的方式是利用他提供的接口快速生成表单内容,若样式不满足需求可以适当进行微调,而不是让他逐个拖入表单/表单项/输入框/单选 等)
由于篇幅优先本文先讲述了:UI 代码生成 及 组件/页面将数据通信;对于数据源及事件部分下面文章将会注意阐述。回到本文提出的技术方案大致思路:
UI 代码生成: 可视化编辑器使用 schema 渲染并生成 schema,工具根据 schema 生成前端代码;其主要利用 vue render 的结构去生成 template 部分,由于静态属性较多通过 id 进行统一绑定;动态属性部分通过
$page.data
获取当前页面的data
的值;其中还包括 动态属性获取/修改,路由注册等;数据通信: 父子间通信使用
props/emit
,跨组件通信使用vuex
,实现了类似于mapState
的功能mapPageState
,具有有传统 vuex 开发一样的方式,computed
中获取,使用commit
方式修改 state
设计方案的原始初心是:不额外引入新的技术栈/框架,利用现有的能力去快速生成页面进行提效。其方案可能有部分考虑不周/不当,欢迎大家指正!
- 本文链接: https://mrgaogang.github.io/lowcode/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96%E6%8F%90%E6%95%88%E6%96%B0%E6%80%9D%E8%B7%AF.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!