前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >低代码引擎实战 - 从零封装低代码组件

低代码引擎实战 - 从零封装低代码组件

原创
作者头像
PHP开发工程师
发布2022-05-23 10:47:05
9860
发布2022-05-23 10:47:05
举报
文章被收录于专栏:thinkphp+vue

一、 Container

构造页面时需要给其他组件一个容器来包裹,先用 vant 的 Card 组件来封装我们的容器组件 Container。

src/components 目录下新建 Container 文件夹,再创建 Container.tsxindex.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 导出

代码语言:javascript
复制
export type {ContainerProps} from './components/container'
export {default as Container} from './components/container'
复制代码

运行命令 npm run lowcode:dev 会看到跟 src 同级的目录 lowcode 目录下多了个 container 文件夹,里面有个 meta.ts 文件,这是根据代码生成的组件描述文件,在拖拽使用这个组件时,低代码引擎根据这个描述文件来解析组件。

代码语言: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": 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 属性想要枚举值,只有 rowcolumn 两个属性值。查询支持的设置器,发现 RadioGroupSetter 可以满足需求,按照定义写我们自己的属性和设置器

代码语言:javascript
复制
{
  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.tsxindex.tsxindex.scss 三个文件

代码语言:javascript
复制
// 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 属性即可。

代码语言:javascript
复制
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 封装好,会使用到几乎所有的设置器。

由于时间关系,先只暴露 dataSourcecolumns 属性,通过 columns 属性,我们将学会如何使用 ArraySetter 动态设置数组。通过 dataSource 属性,我们将学会使用 MixedSetter 使属性支持多种设置方式。

本组件基于 antd 的 Table 扩展。

src/components 目录下新建 Table 文件夹,然后新建 Table.tsxindex.ts 文件

代码语言:javascript
复制
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 上注册组件,否则看不到效果。

代码语言:javascript
复制
export type {JTableProps} from './components/Table';
export {default as Table} from './components/Table'
复制代码

运行 npm run lowcode:dev,会在 根目录/lowcode 下生成 table 文件夹,里面的 meta.ts 就是组件的描述文件。

由于我们暴露出的属性 dataSourcecolumns 是复杂结构,自动生成的描述不能满足需求,所以手动更改描述文件:

代码语言:javascript
复制
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 就会正常加载,项目也就不报错了。

这种方法有个缺点,在组件库封装过程中,其实是看不到效果的,因为渲染不出来。只有在具体使用组件库的时候,才会渲染出来,调试不方便。

总结

其实自定义封装组件,总结一下就三步:

  1. src/components 文件夹下新建组件的文件夹,写逻辑代码,定义需要对外暴露的 props 。
  2. 根目录/index.tsx 中注册组件。不注册的话页面上看不到。
  3. 运行 npm run lowcode:dev 命令,会在 根目录/lowcode 目录下自动生成组件的描述文件 meta.ts,简单类型的 props 比如 string、bool 一般没啥问题,如果是复杂类型,比如复杂对象、数组,自动生成的描述可能不是我们想要的,这时需要手动改描述文件。

前两步我们都比较熟悉,重点主要在第三步改描述文件。在页面上对组件进行拖拽、配置时,支持的操作都是由描述文件定义的。描述文件的重点是设置器,一个属性支持怎样的交互,是可以输入文字,还是下拉框,还是可增删的数组,都是由设置器定义的。

设置器 Setter 的文档在 这里,里面包含了所有官方提供的 Setter。据平时的经验看,官方的设置器能满足 90% 的日常需求。当然还支持自定义 Setter,这部分我还没研究,可以查看官方文档。

官方的 demo 又更新了,新增了 antd 所有组件的支持,如果没有特殊需求,直接用官方提供的组件省时省力。

这个低代码引擎感觉还是在原型阶段,官方的文档、demo 会时不时更新,及时关注 crmeb 可能会有意外收获。

源码附件已经打包好上传到百度云了,大家自行下载即可~

代码语言:javascript
复制
链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
提取码: yu27

百度云链接不稳定,随时可能会失效,大家抓紧保存哈。

如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~

开源地址

码云地址: http://github.crmeb.net/u/defu

Github 地址: http://github.crmeb.net/u/defu

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 Container
  • 二、Panel 组件
  • 三、Table 组件
  • 四、坑点
  • 总结
  • 开源地址
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档