1.前言
之前用过antd.pro
框架来实现前端开发,不管是v4
还是v5
都封装的不错,比如今天要说到的表格。
在日常开发中,少不了增删改查,有了这些东西,就不得出现三个必要的组件
- 表格组件
- 查询组件
- 分页组件
而,antd.pro
怎封装了一套表格组件,包含上面三个组件。至于为什么不直接把antd.pro
中的表格组件直接拿来用呢?
在前段时间研究微前端
的时候,发现antd.pro
对微前端
的支持并不友好,为了以后方便管理前端项目,微前端
是迟早要用的。antd.pro
对微前端
支持不友好的原因是:微前端在管理子模块的时候,需要子模块在主入口文件中对外(微前端)暴露自己的生命周期函数,这样微前端框架才能获取到当前的应用生命周期
。
而antd.pro
的缺点就是封装的太彻底,以至于我们在使用的时候不需要关心他的主入口文件
,它会动态生成主入口文件
。这就导致我们无法找到应用的入口文件,也就无法对外暴露生命周期函数。
基于此,不得不更换为其他的框架。由于我们要做的项目跟权限管理有关,所以就采用了react-admin
。而react-admin
也封装了很多优秀的组件,但时预留了webpack
和主入口文件
的配置,所以以后往微前端
上集成的时候也比较方便。
然而,有一些通用的组件还是没有做好封装,比如对表格的分装的就不够好。那么在所有的页面上都会有大量重复性的表格出现,而且表格跟按钮组的联动也会重复出现在很多模块中,这就导致代码看起来非常的low。
比如下面这个表格相关的操作:
一共可以分为四个部分:
- 查询表单
- button按钮组
- 表格
- 分页
这四部分如果你不进行封装,就可以想到每个模块都会出现大量重复性的代码。作为一个有洁癖
的程序员,奥不,应该说程序员都有洁癖。对于这样的代码是不能容忍的。
所以就着手做一下封装。
2.Table组件封装
2.1 组件封装
Q:什么叫组件封装呢?
A:其实封装跟抽象意思很相近,将有相同属性的事物统一封装到一个对象中或者组件中,以便在受益于大众。
Q:组件的思想是什么呢?
A:组件的核心应该是通用,而不是定制。
Q:什么是通用?什么是定制?有什么不一样么?
A:通用就是所有的人都能用,定制就是私人化。我们简单的抽象出一个人
组件,每个人都有鼻子有眼,能哭,能跑,能笑等,这些应该是个人都会有的。但拿一件事来说,你比如跑这个动作,既然你作为人组件的一个方法,那么针对不同的人你需要进行不同的操作,比如有的人跑得快,有的人跑的不快,有的人出国车祸截肢了,他不会跑。这些情况你都需要考虑到,但是你需要讲这些都在组件中进行处理么?
很显然不是!!!!
我们需要对外暴露组件的属性,然而通用组件的封装应该是有共性的,共性就是所有调用它的外部组件都有的属性,而不是针对每个组件去单独处理其特殊的地方。我们需要做的就是暴露出去共性,至于特性,那么交给调用组件者自己去处理这个问题。
起初我在做组件封装的时候一直困在table
和button按钮组
之间的联动问题上。因为不同的模块,按钮组是不同的,比如用户模块,他有增删改查禁,而日志模块只有查看和删除。而不同的按钮有不同的处理逻辑。
- 比如说删除按钮的逻辑应该是表单是否勾选,更复杂的可能是选中的数据是否被其他模块引用
- 比如启用/禁用按钮,只有当选中的数据是启用状态时才能禁用,禁用的数据也只能启用
- …
为了方便table和按钮组的联动问题,我一开始是将按钮组放到组件中,将所有的按钮种类罗列了一遍,然后根据父组件传递的权限爱控制按钮的显示隐藏。功能也能实现,但是看起来额外的别扭,因为不同的按钮有不同的处理逻辑,这样的话所有的处理逻辑都会在子组件中进行处理,那么这就成了我们前面所说的定制/特有
而不是通用
。所有想了再三,最后还是将button按钮组的定义放到了外面。
2.2 定义组件
思考下,如果我们需要封装一个组件,那么我们需要怎么做?
我们封装一个表格组件,那么组件中包含:
- 能够生成搜索表单的属性
- 能够生成表格的属性
- 能够生成按钮组的属性
- 能够生成分页的属性
- 能够回调的函数
有了上面的思考,我们就有了想法。我们首先看一下表格,因为所有的操作都是依赖于表格的,不管是查询还是分页,不管是增删改,都离不开表格,所以只要我们需要把表格组件的属性搞明白,那么其他的部分就自然而然的小意思了。
2.2.1 表格的封装
由于我们还是基于react+antd
,所以最基本的还是要基于antd的table组件
。
一个table
的组件应当包含最基本的两部分:
- 数据源DataSource
- 列columns
数据源是通过接口向后台请求来的,column是根据后台返回数据的属性定义的。所以我们就需要定义这两个部分
const columns = [
{
title: '部门名称', dataIndex: 'name', type: 'input' },
{
title: '上级部门', dataIndex: 'parent_name', type: 'select-tree',
options: departments, style: {
width: "250px" },
render: (value) => {
return value ? <div styleName="tags-style">{
value}</div> : <Tag color="#f50">无</Tag>
}
},
{
title: '创建时间', dataIndex: 'createtime', type: 'date-range',
render: (value) => {
return dateFormat(value * 1000)
}
},
{
title: '修改时间', dataIndex: 'modifytime', type: 'date-range',
render: (value) => {
return dateFormat(value * 1000)
}
},
{
title: '状态', dataIndex: 'status', type: "radio-group",
options: [{
label: '启用', value: 'enabled' }, {
label: '禁用', value: 'disabled' }],
render: (value) => {
if (value === "enabled") {
return <Tag color="#108ee9">启用</Tag>
} else {
return <Tag color="#f50">禁用</Tag>
}
}
},
{
title: '操作', dataIndex: 'operator',
render: (value, record) => {
const {
id, name } = record;
const items = []
if (operations.detail) {
items.push({
label: '查看',
icon: 'eye',
onClick: (e) => {
e.stopPropagation();
this.setState({
visible: true, id: record.id, record, type: 'detail', drawerTitle: '部门详情' });
},
})
}
if (operations.modify) {
items.push({
label: '修改',
icon: 'form',
onClick: (e) => {
e.stopPropagation();
this.setState({
visible: true, id, record, type: 'edit', drawerTitle: '修改部门' });
},
})
}
if (operations.delete) {
items.push({
label: '删除',
color: 'red',
icon: 'delete',
confirm: {
title: `您确定删除"${
name}"?`,
onConfirm: (e) => {
e.stopPropagation();
this.handleDelete(id);
},
},
})
}
return <Operator items={
items} />;
},
},
];
那么一个表格的雏形应该是这样的
父组件:
<DynaroseTable
loading={loading}
columns={columns}
dataSource={dataSource}
total={total}
/>
子组件:
<Table
loading={
loading}
className="components-table-demo-nested"
rowClassName={
record => {
if (record.id === selectedRoleId) return 'role-table selected';
return 'role-table'