低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

低代码之光!轻量级 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 的主要用途就是将配置项转换成图形化控件,方便调试参数。

低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

除了 dat.gui 之外,还有其它几款 GUI 项目也做得不错,tweakpane、lil-gui、leva。

低代码平台中的配置栏

对于使用过低代码平台或者开发过类似产品的朋友来说,低代码平台的布局已经司空见惯,在布局的右侧通常都是配置栏。当然,我们使用的很多软件也是如此。随便贴几张主流工具的截图。

低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

首先说一点,并不是每一款低代码产品都需要 GUI 生成配置,比如第一张截图 Webflow,它的所有组件的配置项都是一样的(全部是 CSS 的可视化配置),这种情况直接写一个公共组件可能更简单。

但是像第三张截图 Looker Studio 这样的产品,每一种图表组件的配置都不一样,同时还允许用户自定义组件,那么这类产品就非常需要一套灵活易用的 GUI 库了。

在 Acrodata GUI 的文档站首页,我用 GUI 创建了一个稍微复杂的 CSS 渐变生成器,它和低代码平台中的配置栏非常类型,欢迎把玩尝试。

低代码之光!轻量级 GUI 的设计与实现(轻量化代码)

查看 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,比如 reactjsonschema-form。

另外 JSON Schema 的格式较为复杂,组件的配置项与 JSON Schema 的映射关系非常不直观。

基于响应式表单构建 GUI

Acrodata GUI 是基于 Angular 的响应式表单构建的,核心代码只有大约 200 行(查看源码)。

为了方便在模板中遍历数据,首先需要将 GUI config 对象转换成数组,同时使用响应式表单的 registerControlFormGroup 的实例中注册所有表单控件。然后在模板中使用响应式表单的指令 formGroupNameformControlNameformArrayName 绑定不同 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

相关新闻

联系我们
联系我们
公众号
公众号
在线咨询
分享本页
返回顶部