仅限于个人理解
低代码是一种技术。在说它之前,先讲下前端这几年的发展,看看它是如何一步步“走向”低代码的。
前端的发展
前端的角色大致经历了这3个过程:切图仔 —— 前端开发 ——前端工程师
传说中的“切图仔”
很早之前,大家都说前端只是个切图仔,是有原因的。
那时候,Web页面分为静态页面和动态页面。
静态页面就是干干净净的HTML页面。
动态页面是指由后端Web框架生成的页面,大家可能听说过这些技术标准或者框架,比如:ASP.NET、Java的JSP、Phthon的Diango、Nodejs的Express。后面以JSP作为代表。
细心的同学可以瞅一瞅我们浏览器的URL的后缀,有时候它是HTML,有时候是JSP的, 也有可能是php的。
浏览器都是通过HTML + CSS渲染页面的。所以动态页面就是通过后端语言动态的生成HTML/CSS。
那个时候的分工是,后端开发写好JSP页面,前端同学写写CSS,而CSS中有很多背景图,也就是传说中的切图仔。
那时候也是有“低代码”工具的,它叫Dreamweaver。可以拖拽形成页面,然后生成HTML。
前端开发
在上面的历程中,你会发现这样一种现象:JS的比重越来越大。
静态页面中几乎没有JS。
早期的动态页面中有少量的JS,而且几乎都用于特效。
在JSP中,前端的工作和后端的工作是耦合到一起的。后端输出HTML,前端输出CSS和JS,前后端要紧密联系到一起。随着交互越来越复杂,前端对HTML的输出要求越来越高,后端越来越力不从心。
前端还要解决由各种浏览器造成的环境问题。不同的浏览器,对于HTML/CSS的渲染、JS的解析可能是不一样的。
前端的各种框架疯狂出现,需要不断地学习,写jsp的后端已经快变成“写前端页面的后端”了。
这个时候就有了前后端分离的概念。专业的人干专业的事儿,前端不再是附属,后端也省掉了很多事儿,大家都很开心。直到Node.js的出现,前端开发也能写服务端程序,也能干“后端”的事儿了。两个角色彻底对等。
前端工程师
要写的代码实在是太多了,程序员一直追求的就是就是将一个大的问题分解成一个个小的问题去解决,也就是模块化。前端开发也是如此。
前端开发们一直在想着如何将(HTML/JS/CSS)模块化,让各个模块之间解耦。
这里讲的(HTML/JS/CSS)模块化,它是指页面的某个模块,包含(HTML/JS/CSS),解决的是模块复用的问题。跟JS的模块化(AMD/CMD、ES6模块化和NodeJs的CommonJS规范)是有区别的。
前端中的模块化
JSP中的模块化
在JSP中,我们可以通过字符串拼接的方式生成HTML。比如输出Hello World:
<html><body>Hello, World!</body></html>
为了更好地生成HTML,后端都使用的模板语法,其本质都是简化字符串拼接地过程。比如这一段JSP 代码:
<html><body><% out.println(Hello World!); %></body></html>
JSP甚至还能不断地嵌套,以解决HTML复用性问题。比如:一个系统有10个页面,每个页面的头部和底部是一样的。那就可以定义一个header.jsp和footer.jsp,用于公用。
虽然每个jsp里面都可以有HTML/CSS/JS,相当于一个独立的模块,但是它始终解决不了一个根本性的问题:如何让(HTML/CSS/JS)形成一个独立的模块,相互之间不干扰?
所有JSP页面的HTML/CSS/JS都在一个域里面,它们之间可以相互干扰。当一个系统足够庞大时,这种干扰就像一根根丝线,越来越多,越缠越紧密,直到你根本理不清楚。
通过JS将(HTML/CSS/JS)模块化
既然JS也可以操作HTML和CSS,那为什么不用JS去拼接HTML了?
其实是可以的:将前端页面分解成各种组件,每个组件都是个JS函数类。在类初始化时,会初始化它的HTML和CSS。
这种做法的好处是:我可以通过JS代码自由组合这些组件,生成我们的页面。
它的缺点在于代码的阅读性和可调试性会非常差。当前端开发者发现页面的元素有Bug时,可能都不知道HTML的渲染代码写在哪儿。
它看着跟JSP的方式很像,但为啥一开始就不是这样的呢?
那是因为N年前大家的网络环境都很差,带宽能省一点就一点。请求JS文件再通过JS去渲染HTML,比直接访问后端JSP获取HTML多了一步。在这段时间内,页面会白屏。而且搜索引擎公司的网络爬虫无法识别JS中的HTML内容,会让你的网站在搜索引擎中的排名落后。
通过工程化将(HTML/CSS/JS)模块化
Node.js让前端拥有了工程化的能力,我们可以通过一些中间模板文件生成JS。以Vue为例,这个vue文件是这样的,它最终会打包生成js:
它真正解决了(HTML/CSS/JS)模块化的问题,可以让(HTML/CSS/JS)内聚在一起作为一个独立的功能模块,也能解决代码阅读性和调试的问题。这就是前端的组件化。
在生成JS的过程中,我们还可以加入各种各样的工具,提升我们的效率。比如:
1)Babel:它可以让你使用最新版本的JS进行编码,转变为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
2)Sass:可以转换为完全兼容所有版本的CSS,还有很多拓展的功能和特性。
模块跟数据绑定到一起
在Vue中,有个”数据双向绑定“的概念。当改变数据,视图会发生改变;当用户操作视图时,数据也会发生改变。它改变了开发写JS的模式:
同时还衍生了一个概念:面向数据开发。在开发一个页面之前,我们应该先定义好它的数据结构,再根据这个数据结构映射对应的页面。这个数据结构,就是后端的模型数据 + 前端功能拓展的数据。
走向低代码
既然前端都面向数据开发了,是根据数据的属性映射不同的组件的。是不是就可以把数据模型跟视图页面进行映射?
答案是肯定的。
多维表格就是非常典型的代表。多维表格本身是个在线表格,拥有了数据存储的能力。它可以通过数据列字段的映射,生成一个”添加数据“的表单视图。通过维度分析,还能自动生成统计图表视图。
通过数据的属性映射,我们是可以直接生成数据的增/删/改/查页面的。
既然功能是可行的,那么难点究竟在哪儿呢?
UI
首当其冲的就是布局问题。根据拖拽时子组件的位置来划分,布局包含以下几种:
这个地方的布局是指功能,跟前端的一些概念(静态布局、自适应布局、流式布局、响应式布局、弹性布局)是有差异的。
1. 无布局:正常的文档流布局,按从左往右,从上往下排列。
2. 绝对定位布局:拖到哪儿是哪儿,上层的组件会盖住下层的组件。界面很容易七扭八歪的。
3. 栅格布局:相当于设置子组件宽度的百分比,在不同的屏幕上有一定的适应性。
4. 表格布局:这个模块类似于一个表格,每个子模块都在格子里面。
单一的布局都是比较简单的。比较麻烦的是布局之间可以切换,比如:父组件先设置为绝对定位布局,然后改成栅格布局,你还得去根据宽度去计算每个子组件的栅格数。~嗯,想想都脑阔疼。
其次是在用户操作的过程中,会发生UI的变化。比如:当数字大于阈值时,字体设置为不同的颜色(红/黄/绿)。我们可以通过js去控制,但有些时候辅助设置CSS会更加方便。这时我们就需要在父组件中设置子组件的样式。
对于样式的修改,我们通常有3种方式:
1. 全局样式修改
2. 组件内样式修改,包括对子组件样式的修改。
3. 子组件样式的修改。包括写css和内联样式。
这3种方式,都是0到1很容易,但是让它有效(从1到100)却很难。后面会详细介绍。
交互性
其实,在线Excel就能实现了数据的增/删/改/查功能,只是它的操作界面都是“格子”。按照页面来看的话,它的UI&交互性比较差。
比如:我们需要输入物料的编号这个功能,根据UI&交互的复杂性由低到高,它可以有以下视图:
1. 文本框:直接手动输入
2. 下拉选择框:可以下拉搜索(比如根据编号搜索)。
3. 物料选择框:可以下拉搜索,也可以点击弹出框进行更复杂的条件查询(比如根据名称搜索)。
一个模块可以分为各个子模块、子模块之间的联系、父与子的联系。
对于单个子模块而言,它的UI&交互性很好解决。模型的数据类型是有限的,我可以根据每类数据开发一或多个组件。这些组件的UI&交互性甚至可以根据具体的需求进行定制。
针对上面“输入物料编号”的功能,我们可以提供多个组件去选择:通用的功能组件”下拉选择框“和定制业务组件“物料选择框”。
其实难点是子模块之间的联系如何去处理,也就是组件之间的交互。在前端,我们称之为:组件之间的通信问题。当一个组件的状态发生改变时,我们需要去改变页面其它的组件或者页面本身。这些改变包括但不限于:
以一个国家选择框A 和一个城市选择框B的交互为例。
1. 让其它组件的数据源发生变更。比如:当A的值发生改变时,B的数据源要发生一定的改变。
2. 让其它组件的值发生变更。比如:当A的值发生改变时,B默认选中它的省会城市。
3. 让其它组件的样式发生变更。比如:当A的值发生改变时,B中的省会城市标红加粗。
4. 让校验状态发生变更。比如:当A的值为空而校验规则不能为空时,应该通知页面“国家不能为空”。
父子之间的通信与子模块之间的通信问题差不多。可以通过“子—父—子”来实现子组件之间的通信。
业务逻辑的复杂性
程序之所以千千万,就是因为业务千千万。两家公司都是物流公司,主体流程也是相同的,但是内部的系统功能可能是千差万别。归根到底,业务逻辑是人想出来的,而人类有着丰富的想象力。
在上面提到的“让校验状态发生变更”中,根据业务的复杂性,模块之间的关系可以更复杂。比如:
1. 当点击“国家选择框”时,我们需要判断用户有没有先选择工厂,没有的话就给予错误提示。
2. 当选择了工厂没有选国家,和都选择了工厂和国家,错误提示可能又是不一样的。
当然,我们也可以把A和B封装成一个组件C,将交互的复杂性都放到C里面。此处只是作为一个例子。
或者像下图中的物料树,它的行与行之间、各个列之间都存在联系,校验函数的主体代码都有几百行。
根据业务逻辑的复杂性,我们可以分为:
1. 简单:通过配置就可以实现,比如:显示/隐藏。
2. 普通:通过逻辑编排器来实现,比如:分数为90分以上的是A,80分以上的是B,60分以下的是D。
3. 复杂及以上:只能写代码,比如:上面物料数的校验。
低代码之所以不能无代码,很大程度上就是因为业务逻辑的复杂性只能通过写代码去解决。
有些低代码平台比较容易实现,是因为它的场景都是可以固化出来。它意味着你的UI、交互性、业务逻辑很多都是固定的。比如大屏看板,以下问题都可以固化:
1. 布局问题。“表格布局”就可以解决,都用不到其他布局方式。
2. 组件的类型。组件的类型很有限,主要是跟图和表。也没有弹出框这种全局组件。
3. 交互。组件之间基本没什么联系,因为大屏只是展示用的,它不需要用户操作。
4. 业务逻辑。复杂性全部内聚在组件中,对于用户来说,业务逻辑复杂性为0。
根据这些固化的场景,我们还可以总结出一个个通用的模板,加速页面的制造过程,让用户十分钟就能造好一个页面。
基本上,场景的固化程度越高,低代码的建设难度越低。
这个地方不是说低代码平台的建设难度低,而是指平台中低代码的部分。比如多维表格,它的核心能力还是表格,在线Excel基本上就是前端技术的天花板了。
场景的固化程度从低到高,依次可分为:
1. 不使用可视化,只用修改配置。这种低代码平台只用一个配置管理界面就可以了。比如电商的活动页,页面的布局和组件的类型都一样,有区别的地方只是组件里面的内容(文字和图片)。大屏系统也可以做到用一个配置管理页面来实现,可视化拖拽只是为了让用户更直观地看到页面的布局。
2. 无代码。比如 BI报表和多维表格,无编程经验的人就能快速生产出一个报表页面。BI报表的界面之所以看起来比较粗糙,是因为它的重心是在数据图表上面。BI不光是为了数据呈现,它的愿景是数据分析和数据预测,里面内置了大量的有价值的图表模板,也就是分析模型。至于多维表格,它的表单几个月之前还是通过配置生成的,现在多了可视化拖拽的功能。它的在线Excel编辑功能已经解决了数据设计、存储、查询的问题,视图编辑上正在往低代码方向上走。
3. 低代码。低代码一定会涵盖无代码的场景,只是无代码的场景比较简单。嵌入一定的代码,是为了解决更复杂的问题。这几年疯狂涌现的低代码平台,包括部门引入的BPM项目,基本上就归于此类。它们都要解决这几类问题:
1)页面的布局问题。最简单的就是绝对定位,它的适应性也最差。如果想实现屏幕自适应,栅格布局是现在比较流行的做法,对于同一个组件,可以在大/中/小屏幕上设置不同的栅格,用以决定组件的宽度。当然,如果能支持多种布局,那就再好不过了。
2)组件的层次问题。按照组件化的思想,一个组件里面可以包含多个子组件,然后这些子组件又可以包含孙组件,然后无限嵌套下去。很多视图设计器产出的页面都是一层的,它没有容器的概念。比如:现在很多项目的表单设计器,它产出的一个页面只能是一个表单。它不能把这个表单当做一个容器,然后容器里面再嵌入其他的表单,然后一直递归下去。判断一个组件的层次问题其实很简单,就看它有没有无限嵌套的容器组件。多维表格是不能嵌套的,BPM中可以嵌套一层。
3)组件类型的拓展问题。在低代码的难点问题中,业务逻辑的复杂性可以通过自定义组件来解决。每个团队都有自己熟悉的组件库,而且在正常的业务开发过程中,我们还需要创建大量的业务组件。一个低代码平台如果能够支持自定义上传组件,那么它就具备了无限拓展的可能性。
4)植入JavaScript代码的问题。在开发过程中,需要在必要的时候植入代码。这个必要的时候是指:各个组件的生命周期钩子(初始化/销毁)、监听用户操作的事件。植入JS代码的广度和代码可操作的灵活度,也决定着低代码的难度。植入JS代码的广度是指在哪些地方可以植入代码,最简单的就是在页面加载时植入代码;代码可操作的灵活度是指需要解决可调用变量和函数的问题,这些变量和函数可以很简单,也可以很复杂。最简单的植入JavaScript代码,就是这段代码是个纯函数,跟其它变量都没啥关系。这种代码一般可以通过逻辑编排器来实现。复杂点的植入JavaScript代码,它可以与其它组件通信,也能访问系统的公共变量。
5)植入CSS的问题。设置组件的样式是必须的,它的难度在于设置样式的灵活性。比如:设置字体颜色,我加1个配置项就可以。但是CSS的样式属性千千万,不可能每个都加配置项的。而且CSS里面可以相互嵌套,加配置项也可能搞不定。其实有专门的网站做可视化CSS配置的,其难度也不亚于一个低代码平台了。
6)组件之间通信的问题。在前面解决“植入JavaScript代码”的问题中,如果这段代码比较复杂,就需要调用其它组件或者全局的变量和方法。比如:需要调用远程服务端接口,需要获取项目Token和封装好的请求对象。
7)生成代码的问题。生成的代码按类型可以分为开发的代码Vue和编译的代码(js)。按范围可以分为单个页面的代码和可执行的工程代码。之所以要生成工程代码,是因为多个页面的时候,它们是有一些公共环境的。前者只能做参考,用于Copy;后面可以直接运行、打包和部署。
既可以全代码,又可以可视化,两者可以相互切换。刚毕业的时候使用的是一个叫FlashBuilder的编辑器。它可以通过可视化拖拽界面,直接生成代码。修改完代码,再切换到可视化状态,去拖拽界面。现在想想,这个编辑器真是强大。
结语
数字化的愿景是塑造数字孪生世界。数字就是数据,而前端就是数据视图。
低代码平台基本上都会挂上助力数字化的标语。我想,除了营销的噱头外,更多的是因为前端可以帮助人们更快更好的理解这个数字孪生世界。毕竟,数据一直都在那里,只是人看不懂而已。