上一篇文章讲了如何开始使用阿里低代码引擎 low-engine,以及如何在引擎 demo 中引用自定义组件,本篇将基于 vant 和 antd 封装一些低代码组件,带领大家熟悉自定义组件的封装和注意事项。
新建低代码的组件库初始化项目参考文档:lowcode-engine.cn/docV2/funcv…
上篇文章地址:
阿里低代码引擎 lowcode-engine 使用详解 - 开发自定义组件并集成
一、 Container
构造页面时需要给其他组件一个容器来包裹,先用 vant 的 Card 组件来封装我们的容器组件 Container。
src/components
目录下新建 Container
文件夹,再创建 Container.tsx
和 index.tsx
文件
Container.tsx
代码语言:javascript复制import * as React from 'react';
import {createElement} from 'react';
import {Card} from 'react-vant';
import './index.scss'
export interface ContainerProps {
title?: string;
style?: 'object'
direction?: 'row' | 'column'
}
/**
* 由 Card 组成的 container 容器
* @param title
* @param children
* @param otherProps
* @constructor
*/
const JContainer: React.FC<ContainerProps> = ({title, children, direction = 'column', style = {}, ...otherProps}) => {
const _style = style || {} as any;
_style.flexDirection = direction;
const _otherProps = otherProps || {} as any;
_otherProps.style = _style;
return (
<Card>
{title && <Card.Header>{title}</Card.Header>}
<Card.Body>
<div className={'container-wrapper'} {..._otherProps}>
{children}
</div>
</Card.Body>
</Card>
)
}
export default JContainer
复制代码
direction
属性是控制 Container 里面元素的排列方式,对应 flex 布局的 flex-direction
属性。
index.tsx
代码语言:javascript复制import Container from './Container'
export type {ContainerProps} from './Container'
export default Container;
复制代码
然后在 src/index.tsx
导出
export type {ContainerProps} from './components/container'
export {default as Container} from './components/container'
复制代码
运行命令 npm run lowcode:dev
会看到跟 src
同级的目录 lowcode
目录下多了个 container
文件夹,里面有个 meta.ts
文件,这是根据代码生成的组件描述文件,在拖拽使用这个组件时,低代码引擎根据这个描述文件来解析组件。
import { ComponentMetadata, Snippet } from '@alilc/lowcode-types';
const ContainerMeta: ComponentMetadata = {
"componentName": "Container",
"title": "Container",
"docUrl": "",
"screenshot": "",
"devMode": "proCode",
"npm": {
"package": "mini-elements",
"version": "0.1.1",
"exportName": "Container",
"main": "src/index.tsx",
"destructuring": true,
"subName": ""
},
"configure": {
"props": [
{
"title": {
"label": {
"type": "i18n",
"en-US": "title",
"zh-CN": "title"
}
},
"name": "title",
"setter": {
"componentName": "StringSetter",
"isRequired": true,
"initialValue": ""
}
}
],
"supports": {
"style": true
},
"component": {}
}
};
const snippets: Snippet[] = [
{
"title": "Container",
"screenshot": "",
"schema": {
"componentName": "Container",
"props": {}
}
}
];
export default {
...ContainerMeta,
snippets
};
复制代码
默认生成的描述文件,可能不能满足需求,需要拓展。
想要更多自定义配置,有两种方式:
- 在代码中写
propTypes
自动生成 - 手动配置
定义好组件的 Props 之后,运行 npm run lowcode:dev
命令会根据当前定义的 props 自动生成描述文件,基本类型自动生成的描述一般没啥问题,但如果是复杂对象可能会描述不太准确。
注意这里有个坑,只有第一次运行以上命令才会自动生成描述文件,如果这个组件的描述文件已经存在,我们又修改了组件,再次运行命令则不会将新增的属性追加进描述文件中,换句话说以后都需要手动配置了。
有个小技巧可以减轻工作量,如果你没有手动改过配置文件,那修改组件源码后,每次运行前把描述文件删掉,就可以按照最新的 Props 自动生成新的描述文件了。
但是如果按下面的方式手动配置过描述文件,不建议删掉重新生成,之前手动配置的都会丢失。
更改 lowcode/contianer/meta.ts
,想要它成为一个容器,在 component
对象下设置 isContainer 即可。
如果想添加新的属性,或者代码中组件的 props 中定义的属性没有显示出来,则需要手动新增 props。
direction
属性想要枚举值,只有 row
和 column
两个属性值。查询支持的设置器,发现 RadioGroupSetter
可以满足需求,按照定义写我们自己的属性和设置器
{
name: 'direction',
description: '内容的排列方向',
setter: {
componentName: 'RadioGroupSetter',
initialValue: 'column',
props: {
options: [
'column',
'row'
],
}
}
}
复制代码
完整的定义如下:
代码语言:javascript复制import {ComponentMetadata, Snippet} from '@alilc/lowcode-types';
const ContainerMeta: ComponentMetadata = {
"componentName": "Container",
"title": "Container",
"docUrl": "",
"screenshot": "",
"devMode": "procode",
"npm": {
"package": "mini-elements",
"version": "0.1.1",
"exportName": "Container",
"main": "src/index.tsx",
"destructuring": true,
"subName": ""
},
"configure": {
"props": [
{
"title": {
"label": {
"type": "i18n",
"en-US": "title",
"zh-CN": "title"
}
},
"name": "title",
"setter": {
"componentName": "StringSetter",
"isRequired": false,
"initialValue": ""
}
},
{
name: 'direction',
description: '内容的排列方向',
setter: {
componentName: 'RadioGroupSetter',
initialValue: 'column',
props: {
options: [
'column',
'row'
],
}
}
}
],
"supports": {
"style": true
},
"component": {
isContainer: true,
nestingRule: {
// 允许拖入的组件白名单
// childWhitelist: ['ColorfulButton', 'Button'],
// 同理也可以设置该组件允许被拖入哪些父组件里
// parentWhitelist: ['Tab'],
},
}
}
};
const snippets: Snippet[] = [
{
"title": "Container",
"screenshot": "",
"schema": {
"componentName": "Container",
"props": {}
}
}
];
export default {
...ContainerMeta,
snippets
};
复制代码
效果如图,可配置一个 title 属性,如果有值则渲染 Header,没有就不渲染。
还可选择 direction
的值,默认 column。
里面可以拖入其他组件,但仅限白名单里的组件。
二、Panel 组件
先看下效果图,Panel 组件包含两部分:Title 和 Content,重点突出 content 的内容。
右边可配置的属性为:
- title: 标题
- content:内容,一般为数字
- flex: flex 布局下所占的份数,同 css 的 flex 属性,默认 1
在 src/components
下新建 panel
目录,并创建 Panel.tsx
、index.tsx
和 index.scss
三个文件
// Panel.tsx
import React, { createElement } from 'react'
import './index.scss'
export interface PanelProps {
title: string;
content: string;
flex: number;
}
const Panel: React.FC<PanelProps> = ({title, content, flex = 1, children, ...otherProps}) => {
const _otherProps = otherProps || {} as any;
// @ts-ignore
_otherProps.style = otherProps.style || {} as any
_otherProps.style.flex = flex;
return (
<div className={'panel'} {...otherProps}>
<div className={'title'}>{title || 'Panel标题'}</div>
<div className={'content'}>{content || 'Panel内容'}</div>
</div>
)
}
export default Panel
// index.tsx
import Panel from './Panel'
export type {PanelProps} from './Panel'
export default Panel;
// index.scss
@import "./src/variables";
.panel {
display: flex;
flex-direction: column;
.title {
font-size: 12px;
color: $text-minor;
}
.content {
font-size: 28px;
font-weight: 500;
color: $text-main;
}
}
复制代码
同样需要修改生成的 lowcode/panel/meta.ts
文件,一般来说如果只是修改可配置的属性,只需改 configure.props
属性即可。
import { ComponentMetadata, Snippet } from '@alilc/lowcode-types';
const PanelMeta: ComponentMetadata = {
"componentName": "Panel",
"title": "Panel",
"docUrl": "",
"screenshot": "",
"devMode": "procode",
"npm": {
"package": "mini-elements",
"version": "0.1.1",
"exportName": "Panel",
"main": "src/index.tsx",
"destructuring": true,
"subName": ""
},
"configure": {
"props": [
{
"title": {
"label": {
"type": "i18n",
"en-US": "title",
"zh-CN": "title"
}
},
"name": "title",
"setter": {
"componentName": "StringSetter",
"isRequired": true,
"initialValue": ""
}
},
{
"title": {
"label": {
"type": "i18n",
"en-US": "content",
"zh-CN": "content"
}
},
"name": "content",
setter: {
componentName: 'MixedSetter',
props: {
setters: [
'StringSetter',
'VariableSetter',
],
},
}
},
{
name: 'flex',
setter: {
componentName: 'NumberSetter',
initialValue: 1
}
}
],
"supports": {
"style": true
},
"component": {}
}
};
const snippets: Snippet[] = [
{
"title": "Panel",
"screenshot": "",
"schema": {
"componentName": "Panel",
"props": {}
}
}
];
export default {
...PanelMeta,
snippets
};
复制代码
三、Table 组件
在各种组件中,Table 组件是最复杂的了。要把 Table 封装好,会使用到几乎所有的设置器。
由于时间关系,先只暴露 dataSource
和 columns
属性,通过columns
属性,我们将学会如何使用 ArraySetter
动态设置数组。通过 dataSource
属性,我们将学会使用MixedSetter
使属性支持多种设置方式。
本组件基于 antd 的 Table 扩展。
在 src/components
目录下新建 Table
文件夹,然后新建 Table.tsx
和 index.ts
文件
import React, {createElement} from 'react'
import Table, {ColumnsType} from "antd/lib/table";
export interface JTableProps {
columns: ColumnsType;
dataSource: any[];
}
const JTable: React.FC<JTableProps> = ({columns, dataSource}) => {
// 数据处理,防止字段为空
const _columns = columns?.map((col, index) => {
if (!col) {
return {
dataIndex: `${index}`,
title: '列名'
}
}
const {dataIndex, title} = col as any;
return {
dataIndex: dataIndex || `${index}`,
title: title || '列名'
}
})
return (
<Table
dataSource={dataSource}
columns={_columns}
/>
);
}
export default JTable
复制代码
代码语言:javascript复制import Table, {JTableProps} from './Table'
export type {JTableProps}
export default Table;
复制代码
别忘了在 src/index.tsx
上注册组件,否则看不到效果。
export type {JTableProps} from './components/Table';
export {default as Table} from './components/Table'
复制代码
运行 npm run lowcode:dev
,会在 根目录/lowcode
下生成 table
文件夹,里面的 meta.ts
就是组件的描述文件。
由于我们暴露出的属性 dataSource
和 columns
是复杂结构,自动生成的描述不能满足需求,所以手动更改描述文件:
import { ComponentMetadata, Snippet } from '@alilc/lowcode-types';
const TableMeta: ComponentMetadata = {
"componentName": "Table",
"title": "Table",
"docUrl": "",
"screenshot": "",
"devMode": "procode",
"npm": {
"package": "mini-elements",
"version": "0.1.6",
"exportName": "Table",
"main": "src/index.tsx",
"destructuring": true,
"subName": ""
},
"configure": {
"props": [
{
"title": {
"label": {
"type": "i18n",
"en-US": "数据列",
"zh-CN": "数据列"
}
},
"name": "columns",
"setter": {
"componentName": "ArraySetter",
"props": {
"itemSetter": {
"componentName": "ObjectSetter",
"isRequired": false,
"props": {
config: {
items: [
{
"name": "dataIndex",
"setter": {
"componentName": "StringSetter",
"isRequired": true,
"initialValue": "id"
}
},
{
"name": "title",
"setter": {
"componentName": "StringSetter",
"isRequired": true,
"initialValue": "列名"
}
},
]
}
},
}
},
"isRequired": true,
initialValue: [
{
dataIndex: 'id',
title: 'ID'
},
{
dataIndex: 'name',
title: '姓名'
},
{
dataIndex: 'age',
title: '年龄'
},
]
},
},
{
"name": "dataSource",
setter: {
componentName: 'MixedSetter',
props: {
setters: [
'JsonSetter',
'VariableSetter',
],
},
}
}
],
"supports": {
"style": true
},
"component": {}
}
};
const snippets: Snippet[] = [
{
"title": "Table",
"screenshot": "",
"schema": {
"componentName": "Table",
"props": {}
}
}
];
export default {
...TableMeta,
snippets
};
复制代码
效果如图:
columns
是一个数组,我们可以自由的加减列,所以需要用官方提供的 ArraySetter
,使用文档 点这里。每一个 item 都是一个 ObjectSetter
,说实话结构还挺复杂的。
dataSource
支持绑定数据源和直接写 json,所以使用 MixedSetter
。
四、坑点
如果你用的是 antd 组件库,那么会遇到个大坑。
项目中用到了 @ant-design/icons
时,比如在一个组件中引用了某个 icon,会导致组件渲染报错
原因是找不到这个图标组件,查一下加载的 js 资源,发现并没有加载 ant-design/icons
没想到自家的组件库竟然不完全支持!测试发现其他的组件库,像 vant、tea 等都没有这个问题。
暂时还没想到在组件库层面的解决办法,还没找到手动注入 ant-design/icons
的入口。
但是在 demo 中用组件库的时候,找到了解决方案。官方 demo 有个 assets.json
,这里定义了引用的资源,我们可以手动把 icon 添加进去,这样在项目运行时, ant-design/icons
就会正常加载,项目也就不报错了。
这种方法有个缺点,在组件库封装过程中,其实是看不到效果的,因为渲染不出来。只有在具体使用组件库的时候,才会渲染出来,调试不方便。
总结
其实自定义封装组件,总结一下就三步:
- 在
src/components
文件夹下新建组件的文件夹,写逻辑代码,定义需要对外暴露的 props 。 - 在
根目录/index.tsx
中注册组件。不注册的话页面上看不到。 - 运行
npm run lowcode:dev
命令,会在根目录/lowcode
目录下自动生成组件的描述文件meta.ts
,简单类型的 props 比如 string、bool 一般没啥问题,如果是复杂类型,比如复杂对象、数组,自动生成的描述可能不是我们想要的,这时需要手动改描述文件。
前两步我们都比较熟悉,重点主要在第三步改描述文件。在页面上对组件进行拖拽、配置时,支持的操作都是由描述文件定义的。描述文件的重点是设置器,一个属性支持怎样的交互,是可以输入文字,还是下拉框,还是可增删的数组,都是由设置器定义的。
设置器 Setter
的文档在 这里,里面包含了所有官方提供的Setter
。据平时的经验看,官方的设置器能满足 90% 的日常需求。当然还支持自定义 Setter,这部分我还没研究,可以查看官方文档。
官方的 demo 又更新了,新增了 antd 所有组件的支持,如果没有特殊需求,直接用官方提供的组件省时省力。
这个低代码引擎感觉还是在原型阶段,官方的文档、demo 会时不时更新,及时关注可能会有意外收获。