1.为什么要做配置化组件平台?
我们目标是:搭建一个 “共建配置化组件库“的平台,各业务可以共建和使用编排平台配置化的物料库,通过丰富的物料平台,最终达成B端中台low code的想法。
定位:具有”扩展性“、”开放共建“、 ”强大的编排组件“能力,为low code 中台的提供丰富物料。
”配置化组件“ 顾名思义 通过jsonSchema描述生成组件。通过json按照一定的规范将组件组装起来,来满足我们业务不同差异化的需求功能。那这样做能帮我们带来的哪些收益?
- 通过配置化的技术方案,我们可以沉淀大批量的业务组件库,通过统一的规范,以搭积木的方式将各个组件拼接起来,通过这种扩展编排,从而实现对业务场景的描述。
- 通过json编写或可视化编排组件,不需要懂前端,用户可以拓展为更多后端、运营同学,只需要通过简单的培训和规则,就可以组装一个mis页面。
2.配置化组件的技术方案:
2.1名词解释:
基础组件库:官方提供的基于antd二次封装的基础组件库。eg: button、card、select等
自定义组件: 由组件共建者按照组件开发规范,在业务中沉淀的功能发布到平台的组件。
区块:基础组件库或自定义组件是元子组件,通过json将元子组件拼装起来,由平台提供的渲染引擎解析json形成的功能,称为”区块“
官方区块:在业务中遇到场景较多的,官方编排的基础组件库和自定义组件,其他业务方可以”拿来即用“的区块。
自定义区块:有业务方根据业务需求,组合编排的基础组件库和自定义组件。
2.2技术方案:
配置平台提供能力:
平台提供组件的开发规范、接入能力,json渲染器、基础组件库,脚手架、utils函数库
- 平台用户-参与共建组件的同学可以利用,平台提供的脚手架、通用函数库、基础组件库快速创建和开发”自定义组件“,并通过脚手架的能力发布到平台。
- 平台用户-接入平台组件的同学可以利用,平台提供编排组件的能力,按照json规则, 将多个组件进行编排在一起,用户组合编排形成的是jsonSchema,搭配平台的渲染引擎生成的”区块“,就可以集成到自己业务。
业务方接入示例:
// 示例代码 —— 将”区块“集成到自己的业务中的
import { Render } from '@scope/lego-x-core' // 平台提供的渲染引擎
class Page extends React.component {
render () {
<Render schemaJson={{...}}/> // 用户自定义编排组件生成的json, 搭配渲染引擎生成的 ”区块“
}
}
复制代码
3.配置化组件开发规范:
目的:开发规范的制定,为了便于统一收敛配置化组件,让编排组件的同学体验一致;让组件本身具有强扩展性。
依赖:鉴于我们目前的业务项目中多数以是以antd3和antd4为基础组件库,所以我们的配置化组件库选型基于antd组件库二次开发,并兼容antd3、antd4。
3.1schema规范
{
id: '1', // 组件的唯一id
xComponent: 'Acomp', // 组件的名称
version: '1.0.2',
props: {
visible: true, // 是否展示
cols: [ // 普通的props数据
6,
11
],
antdProps: {}, // 继承antd的属性
},
children: [ // 嵌套关系
{
id: '2',
xComponent: 'Acomp',
props: {
id: 1,
onChange (value) {
// 每个组件继承基类组件,$emit每个组件对外暴露的公共钩子
// 通过调用函数方式:实现一对一或一对多联动
if (value === 2) {
this['3'].$emit({
visible: false
})
} else (value = 3) {
this['3'].$emit({
disabled: true
})
}
},
tpl: '这是文档${uid}', // 模板-自己组件data
antdProps: {}
},
},
{
id: '3',
xComponent: 'Bcomp',
props: {
visible: true,
id: 1111, // 普通的props的数据
onClick () {
const res = getApi('http://www.baidu.com');
this['4'].$emit({
dataList: res
})
},
getDataApi: { // Api请求
url: 'http://www.baidu.com?id={this['2'].state.uid}' // 模板
method: 'get'
}
}
},
{
id: '4',
xComponent: 'Ccomp',
props: {
state: {dataList: []},
}
}
]
}
复制代码
3.2组件如何通信
- 场景一:
选项1选A时,选项2是隐藏的状态;选项1选B时,选项2是disabled的状态
- 场景二:
选项2在onChange的时候,请求接口的返回数据,给选项3
分析:”日常开发的业务组件“:是通过props进行数据驱动,props的数据变动,子组件进行重新渲染。 配置化组件:是通过一套jsonSchema进行描述的,jsonSchema确定后就不再改动,但多个配置化组件要想实现联动,就需要有统一的规范触发组件的”props“的变动。
方案:渲染器在解析Json时,渲染器引擎会将jsonSchema的props数据,维护在全局store中。其他组件可以通过$emit方法修改”选项2“组件的props, 选项2“组件的render重新渲染。
{
{
id: '1',
xComponent: 'Acomp',
props: { // 框架会将props维护在全局的store中, 其他组件可以通过操作$emit改变该数据,重新render函数, 进而进行联动。
id: 1,
onChange (value) {
// 每个组件继承基类组件,$emit每个组件对外暴露的公共钩子
// 通过调用函数方式:实现一对一或一对多联动
if (value === 'A') {
this['2'].$emit({ // this指向全局的store; this['2']即组件2维护的全局store
visible: false
})
} else (value = 'B') {
this['2'].$emit({
disabled: true
})
}
},
tpl: '这是文档${uid}', // 模板-自己组件data
antdProps: {}
},
},
{
id: '2',
xComponent: 'Bcomp',
props: {
visible: true, // 维护在全局的store, 其他组件可以通过操作$emit进行联动
id: 1111,
onChange () {
const res = getApi('http://www.baidu.com');
this['3'].$emit({
data: res
})
},
}
},
{
id: '3',
xComponent: 'Ccomp',
props: {
data: "",
}
}
}
复制代码
知识点:上面onClick方法中的this指向是谁? 指向全局store的。 渲染器解析json时,如果发现是函数类型,便会通过bind对该函数重新绑定this —— props.onClick = props.onClick.bind(store)
优点: 扩展性较好,基本可以满足大部分的场景 。
3.3 jsonSchema数据请求规范
api: {
url: 'http://www.baidu.com?id={this['1'].id}',
method: 'GET',
headers: {},
dataType: 'json',// blob、text等
successCallback (res) { //status=200, 命中successCallback
// 处理返回值
// 错误处理, 调用全局的错误处理的方法 this['common'].error('错误了')
return res
}
failCallback (res) { // status !== 200的回调
// 错误处理, 调用全局的错误处理的方法 this['common'].error('错误了')
return res
}
}
复制代码
组件内部(例如上面的Select)往往会涉及到数据接口请求。 数据可以通过外部props获取,也可以通过API shemaJson 规范,内部集成请求接口。 通用的request 请求 由@scope/lego-x-core提供
3.4 布局规范
官方提供的基础配置组件库的包含布局组件—— grid、flex的组件
____Grid
{
id: '1',
xComponent: 'grid', // 类似antd中col的功能
props: {
columns: [
{
span: 6,
offset: 1,
children: [{
id: '2',
xComponent: 'aComp'
}]
},
{
span: 6,
offset: 1,
children: {
id: '3',
xComponent: 'aComp'
}
}]
}
}
____Laoyout
{
id: '1',
xComponent: 'Layout',
props: {
columns: [
{
style: "样式处理" // layout作为父元素,用户可以给aComp的父组件添加样式做布局处理
children: [{
id: '2',
xComponent: 'aComp',
props: {
id: 2
}
}]
}
]
}
}
复制代码
3.5样式规范
配置化组件库一般为多个系统使用,为保证样式样式统一,应该尽可能精简样式,减少样式带来的额外复杂性和视觉成本;
基础组件库和自定义组件均基于antd实现,如需修改样式主题,可以通过《antd-定制主题》实现;
如需特殊处理样式。组件可暴露className、style参数,进行样式复写。
{
id: '3',
xComponent: 'Ccomp',
props: {
style: 'comp-class-name',
dataList: [],
}
}
复制代码
3.6组件开发规范
import {observe, render, utils} from '@scope/lego-x-core' // 核心包内提供基础的
@observe // 装饰器装饰类,当全局的store发声变化时,触发render重新执行
class Acomp extends React.Component {
...
render() {
const {
title,
children // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
} = this.props;
return (
<div className="page">
<h1>{title}</h1>
<div className="body-container">
{render(children) /*渲染孩子节点*/}
</div>
</div>
);
}
}
export default Acomp
复制代码
4. 渲染引擎&基础组件的框架设计
4.1 多包架构
利用lerna进行多包架构。按功能划分为core包、基础组件库功能。npm包的划分及输出文件如下:
底层核心core包
包名为@scope/lego-x-core,其核心功能为“渲染器函数”,另配工具类库(eg: evalTemplate, request请求)。该包会为基础组件、自定义组件开发提供通用能力。
基础组件库:
包名为@scope/lego-x-components,输出npm包和umd模块。
├── build
├── dist
├── lerna.json // lerna配置
├── package
│ ├── components // 基础组件,输出@scope/lego-x-components
│ │ ├── package.json
│ │ └── src
│ │ └── card // 基础组件目录
│ │ ├── demo
│ │ ├── index.js
│ │ ├── lib
│ │ ├── src
│ │ └── style
│ ├── core // 组件底层依赖,输出@scope/lego-x-core
│ │ ├── package.json
│ │ └── src
| | ├── Render // 对接入系统暴露的渲染器,该渲染器与组合编排的jsonSchema,组成”区块“功能,可以接入到业务系统中。
│ │ ├── render // schemaJson规范是支持嵌套组件的,在开发基础组件库或自定义组件时,如果内部可以嵌套其他组件,可以通过render函数解析嵌套组件
│ │ └── utils // 组件工具类库,组件内部使用的函数方法
├── packge.json
├── scripts // 启动脚本
├── site // 展示网站,提供预览功能
└── test // 单元测试、覆盖率测试文件目录
复制代码
4.2 渲染器的设计
4.2.1组件映射到schemaJson
日常开发的React组件:
<Tab>
<TabPane title="tabl">
<Table />
</TabPane>
<TabPane title="tab2">
<Form />
</TabPane>
</Tab>
复制代码
解析成schemaJson:
{
id: '1',
xComponent: 'Tab',
props: {
columns: [
{
title: "tabl"
children: [{
id: '2',
xComponent: 'Table'
}]
},
{
title: "tab2"
children: [{
id: '2',
xComponent: 'Form'
}]
}]
}
}
复制代码
4.2.2 生命周期设计
渲染器主要解析上面的schemaJson。当遇到schema的xComponent字段时,就能明确是哪个组价,按需加载相应的组件,注册组件(增加这一步的原因是:下次遇到相同名的组件无需再次按需引入),解析props进行组件渲染。如果组件有接收到props的children字段,则继续调用渲染器的Render函数进行渲染。
接入系统使用渲染器示例 | 渲染器内部实现示例 | 自定义组件实现示例 |
---|---|---|
![]() |
![]() ![]() |
![]() |
5. 自定义组件自动集成到平台的方案
5.1 开发”自定义组件”的脚手架
- 打包方式采用构建UPM模块发布至CDN的考虑:
-
自定义组件动态集成到平台功能——开发者发布组件后,平台无需上线,按照规则加载相应的cdn地址即可。 复制代码
-
接入系统接入区块时,能按需加载相应的组件,减少包的体积大小 复制代码
- 组件源码收敛到统一的仓库的考虑:
自定义组件具有”开放化“的属性。可能存在同学离职代码交接不及时, 代码不维护等事宜,为了保证组件长期的稳定性,平台会托管源码。
5.2 组件的版本版本管理
5.2.1CDN文件命名规范
包各含版本hash、latest两种类型,用于区分组件版本。例如:
5.2.2基础组件库
任何基础组件更新时均需发npm包,所有组件公用一个版本号。
5.2.3自定义组件
组件更新时需发npm包。该操作必须使用脚手架工具来完成,如果自行使用npm run publish 发包会导致最新的组件改动无法同步至配置平台。
5.2.4 接入系统的用户如何定制版本
{
id: '1', // 组件的唯一id
xComponent: 'Acomp', // 组件的名称
version: '1.0.2', // 指定version, 如果不指定,则使用latest版本
props: {}
}
复制代码
5.3 平台数据库设计
6. 扩展 — low code的目标
7. QA
7.1Q:自定义组件开发时,能否直接使用”antd“组件库?
A: 官方推出的配置化基础组件库是基于”antd“实现,内部兼容了antd3和antd4的版本。原则上”自定义组件库“开发时直接使用官方”基础组件库“即可。
如果官方基础组件库不完善,没有涵盖antd的某些组件,”自定义组件库“也是可以直接使用”antd“的。但是开发时,尽量兼容”antd3“、”antd4″的API.
7.2Q日常开发的业务组件,如何升级成为”配置化组件“?
A:”日常开发的业务组件“:是通过props进行数据驱动,props的数据变动,子组件进行重新渲染。
配置化组件:是通过一套jsonSchema进行描述的,jsonSchema确定后就不在改动,但多个配置化组件要想实现联动,需按照规范进行少量改动。
我们保持着”尽量大家少改代码“的原则,后续会出详细的文档。
7.3 Q组件使用的是”react“开发的,接入系统是Vue, 能否使用接入组件?
针对Vue系统,官方会提供的Vue技术栈的渲染器容器来兼容。
7.4Q”自定义组件”具有“开放化”的属性,组件处于不维护状态了,接入系统有新需求怎么办?
a: 收敛组件源码到统一的仓库,保证代码不会因为”随意乱放“而找不到。
b:组件处于”没人“维护时,组件会在平台展示”下架“状态,让新的接入系统能意识到该组件处于不维护状态。
c: 平台会协调该组件后续的维护者。