其他
面向复杂业务场景下的低代码平台组件设计与实践分享
在低代码平台的应用开发过程中,从创建页面、页面布局到数据连接的一系列能力,都围绕着对 “组件” 的排布和设置进行。由此可见,“组件” 是低代码平台生产应用的 “血肉” 。
如此重要的 “组件”,要怎样才能设计得灵活通用呢?百度智能云低代码平台爱速搭基于百度内外长期线上运行的经验,孵化出了一套能够覆盖绝大多数场景的组件体系。本文将分享我们在整个组件体系设计中的一些思路和心得。
1. 爱速搭中的 “组件” 体系建设
在爱速搭中,“页面编辑能力” 的主要构成如图 1 所示,总的来说可以分为 amis 开源框架 - 可视化编辑器 - 后端组件管理服务 这三大块。图中标蓝的部分,就是本文要重点讨论的 “组件” 相关内容。
1.2 基于 React 生态的 UI 组件库实现的 “联动” 效果
import React, { FC, useEffect, useState } from "react";
import { Radio, Select, Form } from "antd";
import type { RadioChangeEvent } from "antd";
import axios from "axios";
import "./App.css";
const { Option } = Select;
interface OptionData {
label: string;
value: string;
}
const App: FC = () => {
const [value, setValue] = useState(1);
const [options, setOptions] = useState([] as OptionData[]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
axios
.get(
`https://aisuda.bce.baidu.com/amis/api/mock2/options/level2?a=${value}`
)
.then((deferOptions) => {
setOptions(deferOptions.data.data.options);
setLoading(false);
});
}, [value]);
const onChange = (e: RadioChangeEvent) => {
setValue(e.target.value);
};
return (
<div className="App">
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
initialValues={{ remember: true }}
autoComplete="off"
>
<Form.Item label="选项1">
<Radio.Group onChange={onChange} value={value}>
<Radio value={1}>选项A</Radio>
<Radio value={2}>选项B</Radio>
<Radio value={3}>选项C</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="选项2">
<Select style={{ width: 120 }} loading={loading}>
{options.map((item, idx) => (
<Option value={item.value} key={idx}>
{item.label}
</Option>
))}
</Select>
</Form.Item>
</Form>
</div>
);
};
export default App;
图 4 选中选项 A 或者 B 时拉取对应的菜单内容
倘若你是一个对 React 了解不多的开发人员,在制作这样一个效果的过程中,可能会遇到以下事情:
首先需要照着 UI 组件库官网上的教程把 DEMO 跑起来,期间可能会遇到诸如某些全局依赖包没有安装,某些依赖项的版本不对等等教程中并没有写明的问题。 包安装过程中网卡了,安装失败,重试 n 次之后终于成功。 克服一切障碍将 DEMO 跑起来之后,照着官网教程依样画葫芦复制粘贴,少贴了一个尖括号导致编译一直报错,而你由于对 React 不熟悉,纠结好久才发现问题。 什么?React 的组件还分 Function Component 和 Class Component ?Redux 和 MobX 哪家强?Hook 又是什么?useState 和 useEffect ?迷失在生态的海洋里无法自拔。 当你阅读了数吨资料并且掌握了上述全部知识点之后,你已经忘了自己最初到底想干嘛,不过总之,你学会了 React,可喜可贺! 新来的技术负责人表示 React 不好,我们改用 XXX,React 学习进程中断。 换了个技术负责人,表示我们还是换回 React,你去官网拖了最新版,发现和你之前学的已经不太一样,重复前面的过程。
1.3 低代码模式下的联动效果实现
往页面中拖拽一个 radio 组件和一个 select 组件。 设置 radio 组件的字段名,选项和默认值,如图 5。
设置 select 组件的选项源和关联变量(以 ${} 语法形式),如图 6 ,当字段名为 'a' 的 radio 组件的值发生变化时,负责提供 select 组件 “选项” 数据的接口会 “感知” 到这个变更,并触发请求拉取数据。
2. 如何设计一个高度灵活的低代码组件体系
将 “引入组件并通过状态管理方案对前端数据进行管理” 的开发模式转化为声明式的组件描述。 将原本需要由人工编写的,处理组件间关系的 “胶水” 代码转为由平台 / 框架提供配置项实现,例如在上一节的例子中,就以配置的方式代替传统的手工编码,实现了 radio 和 select 组件联动的效果。
在组件库中寻找合适的组件。 在页面上处理组件的排版和嵌套关系,将它们 “拼装” 成一个完整的页面。 处理页面上各组件的联动关系并将接口返回的数据填充到页面的指定位置。
2.1 预置组件集的统筹设计
2.2 组件的树结构嵌套渲染
2.3.2 模板
2.3.3 表达式
2.3.4 基于组件数据共享和传递机制实现组件联动
3. 背后的原理 —— 组件注册机制
export function Renderer(config: RendererBasicConfig) {
return function <T extends RendererComponent>(component: T): T {
const renderer = registerRenderer({
...config,
component: component
});
return renderer.component as T;
};
}
利用该装饰器将组件注册到全局组件池子的语法如下,此处实现的是将一个名为 'my-renderer' 的组件注册到全局的组件池中,注册后在页面配置中就可以以 {type: "my-renderer"} 的声明形式使用该组件。
import * as React from 'react';
import {Renderer} from 'amis';
@Renderer({
type: 'my-renderer',
autoVar: true // amis 1.8 之后新增的功能,自动解析出参数里的变量
})
class CustomRenderer extends React.Component {
render() {
const {tip} = this.props;
return <div>这是自定义组件:{tip}</div>;
}
}
在 amis 框架的源代码中,预置组件也是通过同样的语法注册到全局组件池中,如图 17:
{
"type": "page",
"body": {
"type": "form",
"title": "custom 组件",
"body": [
{
"type": "input-text",
"name": "username",
"label": "姓名"
},
{
"name": "username",
"type": "custom",
"label": "自定义组件",
"onMount": "const button = document.createElement('button'); button.innerText = '点击修改姓名'; button.onclick = event => { onChange('new name'); event.preventDefault(); }; dom.appendChild(button);"
}
]
}
}