低代码之光!轻量级 GUI 的设计与实现(轻量化代码)
前言
每当提起低代码,很多人都会下意识的出现过激反应,吐槽低代码都是**,唯恐避之不及。可能大部分人觉得低代码就是替代手写代码,对于程序员来说这是不可接受的。其实低代码表述的含义非常宽泛,我相信很多人可能都在低代码平台中受益过,而且确实可以提升效率。像原型工具(Figma)、建站平台(Webflow、Framer)、BI 报表(Power BI、Looker Studio)、3D 模型搭建(Spline、Womp)、动画编辑器(Rive)等等,这些都是非常有名的一些在线工具。
言归正传,本文并不是为了介绍低代码平台,也不想评价低代码的好坏,只是想聊一聊低代码平台中 GUI 的设计思路和实现方式。
Acrodata GUI 是一款适用于低代码平台的轻量级 GUI 库,现已开源。
GitHub: github.com/acrodata/gu…
Playground: acrodata.github.io/gui/playgro…
什么是 GUI
GUI 翻译为图形用户界面,是指采用图形方式显示的计算机操作用户界面。在前端编程中,我们一般很少使用 GUI 这样的描述,所以很多人会错误地认为 GUI = UI library。
那么到底什么是 GUI 呢?为了便于理解,我们可以参照前端项目中比较有名的 GUI 项目 dat.gui。做过 3D 可视化或者熟悉 ThreeJS 的朋友一定非常熟悉这个库。dat.gui 的主要用途就是将配置项转换成图形化控件,方便调试参数。
除了 dat.gui 之外,还有其它几款 GUI 项目也做得不错,tweakpane、lil-gui、leva。
低代码平台中的配置栏
对于使用过低代码平台或者开发过类似产品的朋友来说,低代码平台的布局已经司空见惯,在布局的右侧通常都是配置栏。当然,我们使用的很多软件也是如此。随便贴几张主流工具的截图。
首先说一点,并不是每一款低代码产品都需要 GUI 生成配置,比如第一张截图 Webflow,它的所有组件的配置项都是一样的(全部是 CSS 的可视化配置),这种情况直接写一个公共组件可能更简单。
但是像第三张截图 Looker Studio 这样的产品,每一种图表组件的配置都不一样,同时还允许用户自定义组件,那么这类产品就非常需要一套灵活易用的 GUI 库了。
在 Acrodata GUI 的文档站首页,我用 GUI 创建了一个稍微复杂的 CSS 渐变生成器,它和低代码平台中的配置栏非常类型,欢迎把玩尝试。
查看 CSS 渐变生成器源码
轻量级 GUI 的设计思路
由于低代码平台的特殊性和复杂性,GUI 库在设计的时候必须要保证能组合出任意数据结构,同时还要简单易用。
基于 JSON 的配置项
为了支持自定义组件,GUI 库更适合采用 json 数据进行配置。这和上面提到的 GUI 库在使用上有很大的不同,我们以 dat.gui 和 Acrodata GUI 为例说明。
假设某个组件的配置项如下:
js复制代码const options = { content: 'Hello world', opacity: 0.3, visible: false,}
dat.gui 的用法如下:
js复制代码const gui = new dat.GUI();gui.add(options, 'content');gui.add(options, 'opacity', 0, 1).step(0.1);gui.add(options, 'visible');
虽然 dat.gui 的用法很简洁,但是这种函数式的声明方式并不适合动态组件,同时也不利于数据保存。
而在 Acrodata GUI 中的用法则是这样的:
html复制代码<gui-form [config]="config" />js复制代码const config = { content: { type: 'text', name: 'content', default: 'Hello world' }, opacity: { type: 'slider', name: 'opacity', min: 0, max: 1, step: 0.1, default: 0.3 }, visible: { type: 'switch', name: 'visible', default: false },}
上面的 GUI 的配置项和组件的配置项的结构是一样的,只需要将 options 中每个字段的值转换成 UI 控件的 JSON 声明即可。
查看更多基础控件示例
嵌套的数据结构
如果要保证 GUI 可以生成任意数据结构,需要设计五种基础数据(string, number, boolean, object, array)的 JSON 配置项的定义格式。上面的例子中已经展示了三种基本数据类型的定义方式
在 Acrodata GUI 中 object 的定义如下:
json复制代码{ "size": { "type": "group", "name": "Size", "children": { "width": { "name": "Width", "type": "number", "default": 1920, "suffix": "px" }, "height": { "name": "Height", "type": "number", "default": 1080, "suffix": "px" } } }}
最后一种数组类型是最复杂也是最繁琐的。常用数组包含基本数据数组和对象数组,同时每种数组还要支持数组项的动态删减。
下面是一个可动态删减的对象数组的定义方式:
json复制代码{ "series": { "type": "tabs", "name": "Series", "default": [ { "id": 1, "name": "bar" }, { "id": 2, "name": "foo" } ], "template": { "name": "No.<%= i 1 %>", "children": { "id": { "type": "number", "name": "ID" }, "name": { "type": "text", "name": "Name" } } } }}
如果对象数组的数组项不相同,则必须搭配 tab 类型来定义:
json复制代码{ "misc": { "type": "tabs", "name": "Misc", "children": [ { "type": "tab", "name": "Full Name", "children": { "firstName": { "type": "text", "name": "First Name", "default": "James" }, "lastName": { "type": "text", "name": "Last Name", "default": "Bob" } } }, { "type": "tab", "name": "Contact", "children": { "phone": { "type": "text", "name": "Phone", "default": "5550100" } } } ] }}
查看更多组合控件示例
有了上述五种基础控件之后,通过嵌套组合就可以生成任意数据结构了。
JSON Schema 的局限性
为什么不使用 JSON Schema 呢? 很多人可能觉得使用 JSON Schema 定义 JSON 数据会更规范也更通用。这种想法是有道理的,但是 JSON Schema 也有一定的局限性。
首先 JSON Schema 只能定义字段的数据类型,但是无法定义字段的 UI 类型,所以部分使用 JSON Schema 的动态表单方案还会加上 UI Schema,比如 react–jsonschema-form。
另外 JSON Schema 的格式较为复杂,组件的配置项与 JSON Schema 的映射关系非常不直观。
基于响应式表单构建 GUI
Acrodata GUI 是基于 Angular 的响应式表单构建的,核心代码只有大约 200 行(查看源码)。
为了方便在模板中遍历数据,首先需要将 GUI config 对象转换成数组,同时使用响应式表单的 registerControl 在 FormGroup 的实例中注册所有表单控件。然后在模板中使用响应式表单的指令 formGroupName、formControlName、formArrayName 绑定不同 type 的控件就可以了。
Acrodata GUI 使用 Angular Material 作为基础组件库,所有样式和组件都是分模块导入,所以不会产生冗余的代码,其它组件库也可以使用。
开始使用 GUI 组件
html复制代码<gui-form [config]="config" [model]="model" [form]="form" />
config 表示 GUI 的 JSON 配置项,不同类型的控件的配置项稍有不同,详见文档。除了使用 default 定义控件的默认值之外,也可以使用 model(表单值,等同于组件的配置项 options)来定义或更新表单的默认值,这得益于 Angular 响应式表单的 patchValue 方法。
如果你需要监听表单的状态或者值变更,可以使用 form 参数,它可以追踪表单的所有状态变化。
ts复制代码form = new FormGroup({});this.form.valueChanges.subscribe(v =>{...});this.form.get('opacity').valueChanges.subscribe(v =>{...});
总结
虽然上面展示的 GUI 功能很强大,但是 GUI 和动态表单并不能完全划等号,也不是所有的配置项都适合使用 GUI。因为 GUI 的控件类型有限,而且其本身没有复杂的逻辑,所以在低代码平台中要有取舍的使用 GUI 配置。
作者:叙帝利
链接:https://juejin.cn/post/7317489364588855307