每当提起低代码,很多人都会下意识的出现过激反应,吐槽低代码都是**,唯恐避之不及。可能大部分人觉得低代码就是替代手写代码,对于程序员来说这是不可接受的。其实低代码表述的含义非常宽泛,我相信很多人可能都在低代码平台中受益过,而且确实可以提升效率。像原型工具(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 = 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 库在使用上有很大的不同,我们以 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 数据会更规范也更通用。这种想法是有道理的,但是 JSON Schema 也有一定的局限性。
首先 JSON Schema 只能定义字段的数据类型,但是无法定义字段的 UI 类型,所以部分使用 JSON Schema 的动态表单方案还会加上 UI Schema,比如 react-jsonschema-form。
另外 JSON Schema 的格式较为复杂,组件的配置项与 JSON Schema 的映射关系非常不直观。
Acrodata GUI 是基于 Angular 的响应式表单构建的,核心代码只有大约 200 行(查看源码)。
为了方便在模板中遍历数据,首先需要将 GUI config
对象转换成数组,同时使用响应式表单的 registerControl
在 FormGroup
的实例中注册所有表单控件。然后在模板中使用响应式表单的指令 formGroupName
、formControlName
、formArrayName
绑定不同 type
的控件就可以了。
Acrodata GUI 使用 Angular Material 作为基础组件库,所有样式和组件都是分模块导入,所以不会产生冗余的代码,其它组件库也可以使用。
<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 或者有更好的想法,欢迎和我交流!