低代码之光!轻量级 GUI 的设计与实现
前言
每当提起低代码,很多人都会下意识的出现过激反应,吐槽低代码都是**,唯恐避之不及。可能大部分人觉得低代码就是替代手写代码,对于程序员来说这是不可接受的。其实低代码表述的含义非常宽泛,我相信很多人可能都在低代码平台中受益过,而且确实可以提升效率。像原型工具(Figma)、建站平台(Webflow、Framer)、BI 报表(Power BI、Looker Studio)、3D 模型搭建(Spline、Womp)、动画编辑器(Rive)等等,这些都是非常有名的一些在线工具。
言归正传,本文并不是为了介绍低代码平台,也不想评价低代码的好坏,只是想聊一聊低代码平台中 GUI 的设计思路和实现方式。
Acrodata GUI 是一款适用于低代码平台的轻量级 GUI 库,现已开源。
GitHub: https://github.com/acrodata/gui
Playground: https://acrodata.github.io/gui/playground
什么是 GUI
GUI 翻译为图形用户界面,是指采用图形方式显示的计算机操作用户界面。在前端编程中,我们一般很少使用 GUI 这样的描述,所以很多人会错误地认为 GUI = UI library。
那么到底什么是 GUI 呢?为了便于理解,我们可以参照前端项目中比较有名的 GUI 项目 dat.gui。做过 3D 可视化或者熟悉 ThreeJS 的朋友一定非常熟悉这个库。dat.gui 的主要用途就是将配置项转换成图形化控件,方便调试参数。
dat.gui除了 dat.gui 之外,还有其它几款 GUI 项目也做得不错,tweakpane、lil-gui、leva。
低代码平台中的配置栏
对于使用过低代码平台或者开发过类似产品的朋友来说,低代码平台的布局已经司空见惯,在布局的右侧通常都是配置栏。当然,我们使用的很多软件也是如此。随便贴几张主流工具的截图。
Webflow Spline Looker Studio首先说一点,并不是每一款低代码产品都需要 GUI 生成配置,比如第一张截图 Webflow,它的所有组件的配置项都是一样的(全部是 CSS 的可视化配置),这种情况直接写一个公共组件可能更简单。
但是像第三张截图 Looker Studio 这样的产品,每一种图表组件的配置都不一样,同时还允许用户自定义组件,那么这类产品就非常需要一套灵活易用的 GUI 库了。
在 Acrodata GUI 的文档站首页,我用 GUI 创建了一个稍微复杂的 CSS 渐变生成器,它和低代码平台中的配置栏非常类型,欢迎把玩尝试。
Acrodata GUI轻量级 GUI 的设计思路
由于低代码平台的特殊性和复杂性,GUI 库在设计的时候必须要保证能组合出任意数据结构,同时还要简单易用。
基于 JSON 的配置项
为了支持自定义组件,GUI 库更适合采用 JSON 数据进行配置。这和上面提到的 GUI 库在使用上有很大的不同,我们以 dat.gui 和 Acrodata GUI 为例说明。
假设某个组件的配置项如下:
const options = {
content: 'Hello world',
opacity: 0.3,
visible: false,
}
dat.gui 的用法如下:
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 中的用法则是这样的:
<gui-form [config]="config" />
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
的定义如下:
{
"size": {
"type": "group",
"name": "Size",
"children": {
"width": {
"name": "Width",
"type": "number",
"default": 1920,
"suffix": "px"
},
"height": {
"name": "Height",
"type": "number",
"default": 1080,
"suffix": "px"
}
}
}
}
最后一种数组类型是最复杂也是最繁琐的。常用数组包含基本数据数组和对象数组,同时每种数组还要支持数组项的动态删减。
下面是一个可动态删减的对象数组的定义方式:
{
"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
类型来定义:
{
"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 组件
<gui-form [config]="config" [model]="model" [form]="form" />
config
表示 GUI 的 JSON 配置项,不同类型的控件的配置项稍有不同,详见文档。除了使用 default
定义控件的默认值之外,也可以使用 model
(表单值,等同于组件的配置项 options)来定义或更新表单的默认值,这得益于 Angular 响应式表单的 patchValue
方法。
如果你需要监听表单的状态或者值变更,可以使用 form
参数,它可以追踪表单的所有状态变化。
form = new FormGroup({});
this.form.valueChanges.subscribe(v =>{...});
this.form.get('opacity').valueChanges.subscribe(v =>{...});
总结
虽然上面展示的 GUI 功能很强大,但是 GUI 和动态表单并不能完全划等号,也不是所有的配置项都适合使用 GUI。因为 GUI 的控件类型有限,而且其本身没有复杂的逻辑,所以在低代码平台中要有取舍的使用 GUI 配置。
如果你喜欢 Acrodata GUI 或者有更好的想法,欢迎和我交流!