配置化组件平台

1,416 阅读10分钟

1.为什么要做配置化组件平台?

我们目标是:搭建一个 “共建配置化组件库“的平台,各业务可以共建和使用编排平台配置化的物料库,通过丰富的物料平台,最终达成B端中台low code的想法。

定位:具有”扩展性“、”开放共建“、 ”强大的编排组件“能力,为low code 中台的提供丰富物料。

”配置化组件“ 顾名思义 通过jsonSchema描述生成组件。通过json按照一定的规范将组件组装起来,来满足我们业务不同差异化的需求功能。那这样做能帮我们带来的哪些收益?

  1. 通过配置化的技术方案,我们可以沉淀大批量的业务组件库,通过统一的规范,以搭积木的方式将各个组件拼接起来,通过这种扩展编排,从而实现对业务场景的描述。
  2. 通过json编写或可视化编排组件,不需要懂前端,用户可以拓展为更多后端、运营同学,只需要通过简单的培训和规则,就可以组装一个mis页面。

2.配置化组件的技术方案:

2.1名词解释:

基础组件库:官方提供的基于antd二次封装的基础组件库。eg: button、card、select等

自定义组件: 由组件共建者按照组件开发规范,在业务中沉淀的功能发布到平台的组件。

区块:基础组件库或自定义组件是元子组件,通过json将元子组件拼装起来,由平台提供的渲染引擎解析json形成的功能,称为”区块“

官方区块:在业务中遇到场景较多的,官方编排的基础组件库和自定义组件,其他业务方可以”拿来即用“的区块。

自定义区块:有业务方根据业务需求,组合编排的基础组件库和自定义组件。

2.2技术方案:

image.png

配置平台提供能力:

平台提供组件的开发规范、接入能力,json渲染器、基础组件库,脚手架、utils函数库

  1. 平台用户-参与共建组件的同学可以利用,平台提供的脚手架、通用函数库、基础组件库快速创建和开发”自定义组件“,并通过脚手架的能力发布到平台。
  2. 平台用户-接入平台组件的同学可以利用,平台提供编排组件的能力,按照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组件如何通信

image.png

  1. 场景一:

选项1选A时,选项2是隐藏的状态;选项1选B时,选项2是disabled的状态

  1. 场景二:

选项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的组件

image.png

____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 生命周期设计

image.png

渲染器主要解析上面的schemaJson。当遇到schema的xComponent字段时,就能明确是哪个组价,按需加载相应的组件,注册组件(增加这一步的原因是:下次遇到相同名的组件无需再次按需引入),解析props进行组件渲染。如果组件有接收到props的children字段,则继续调用渲染器的Render函数进行渲染。

接入系统使用渲染器示例渲染器内部实现示例自定义组件实现示例
image.pngimage.png image.pngimage.png

5. 自定义组件自动集成到平台的方案

5.1 开发"自定义组件"的脚手架

image.png

  1. 打包方式采用构建UPM模块发布至CDN的考虑:
  • 自定义组件动态集成到平台功能——开发者发布组件后,平台无需上线,按照规则加载相应的cdn地址即可。
    
  • 接入系统接入区块时,能按需加载相应的组件,减少包的体积大小
    
  1. 组件源码收敛到统一的仓库的考虑:

自定义组件具有”开放化“的属性。可能存在同学离职代码交接不及时, 代码不维护等事宜,为了保证组件长期的稳定性,平台会托管源码。

5.2 组件的版本版本管理

5.2.1CDN文件命名规范

包各含版本hash、latest两种类型,用于区分组件版本。例如:

unpkg.com/lego-x-comp…

unpkg.com/lego-x-comp…

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 平台数据库设计

image.png

image.png

image.png

6. 扩展 — low code的目标

image.png

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: 平台会协调该组件后续的维护者。