RadiomM
文章22
标签13
分类1
从一次需求封装,对比函数式组件与模板组件优劣

从一次需求封装,对比函数式组件与模板组件优劣

开发总是领导们的玩具,领导们的需求就是朝令夕改。这不,领导上网随便一搜一个时髦的后台框架,一眼就相中了列展示这个骚功能(注意,下面的实现都是 Vue2。具体如下:

列展示功能展示.gif
由于用的是 Element 框架,本身就不提供这样的方法,没办法,这只能自己去造了。

实现模板表格封装

要实现这样的功能,基本上是需要**多选框表格相互配合好才行,由于用过像 Ant Design Vue 这样的框架,所以我第一时间就想到的是写对象数组**,参考 el-table-column 的属性,我就可以得出下面的数据接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const tableData = [
{
title: "测试",
unit_name: "这是单位",
unit_nature_name: "这是单位性质",
nature_name: "这是职位性质",
},
];

const tableColumn = [
{
prop: "title",
label: "职位名称",
},
{
prop: "unit_name",
label: "单位名称",
},
{
prop: "unit_nature_name",
label: "单位性质",
},
{
prop: "nature_name",
label: "职位性质",
},
];

接着我只需要设计一个组件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<el-table
v-bind="$attrs"
:cell-style="{
height: '76px',
}"
:header-cell-style="{
height: '56px',
backgroundColor: '#FCF2F1',
color: '#573E41',
textAlign: 'center',
}"
>
<template v-for="(column, index) in tableColumn">
<el-table-column
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width"
align="center"
>
</el-table-column>
</template>
</el-table>
</template>

<script>
export default {
name: "HelloWorld",
props: {
tableColumn: {
type: Array,
default() {
return [];
},
},
},
};
</script>


<style scoped>
</style>

那么我就可以实现一个最基础的渲染了表格。如下:

Snipaste_2023-10-27_16-13-08.png
当然,表格的需求往往没这么简单,剩下的需求实现,我就斗胆献丑了,给大家参考一下我的最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<template>
<el-table
ref="caitlynTable"
v-loading="loading"
border
:row-key="rowKey"
max-height="500px"
:cell-style="{
height: '76px',
}"
:header-cell-style="{
height: '56px',
backgroundColor: '#FCF2F1',
color: '#573E41',
textAlign: 'center',
}"
v-bind="$attrs"
@selection-change="selectionChange"
>
<el-table-column
v-if="needIndex"
:key="caitlynKey"
type="selection"
:selectable="selectable"
width="55"
align="center"
></el-table-column>

<template v-for="(column, index) in tableColumn">
<el-table-column
v-if="!column.show"
:key="index"
:prop="column.prop"
:label="column.label"
:width="column.width"
:fixed="column.fixed"
:align="column.align ? column.align : 'center'"
:formatter="
(row, column, cellValue, index) => {
if (!cellValue && cellValue !== 0) {
return '--';
} else {
return cellValue;
}
}
"
>
<template v-if="column.slotHeader" #header="scope">
<slot :name="column.slotHeader" v-bind="scope"></slot>
</template>
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" v-bind="scope"></slot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
export default {
props: {
loading: {
type: Boolean,
default: false,
},
tableColumn: {
type: Array,
default() {
return [];
},
},
needIndex: {
type: Boolean,
default: false,
},
rowKey: {
type: String,
default: 'id',
},
selectionChange: {
type: Function,
default() {
return {};
},
},
selectable: {
type: Function,
default() {
return {};
},
},
},
data() {
return {
caitlynKey: 'caitlynKey',
};
},
watch: {
tableColumn: {
handler() {
this.$refs.caitlynTable.doLayout();
},
deep: true,
},
},
};
</script>
<style scoped lang="scss"></style>

这样的封装已经能实现我项目的所有需求了。由于我需要自己手动改的表格非常的多,就单单这个修改需求,我就整整做了有一个星期有余,我就开始发现了不少缺点。(列展示组件的如何实现不是本文的重点,所以就不展示相关代码)

1、tableCloumn 数据格式属性过多

因为原来的表格都是用 Element 上面的例子做的,所以就会出现很多 el-table-column 的情况,由于需要写一个 tableColumn 的数组,我需要写很多个数据,非常的麻烦,譬如:

1
2
3
4
5
6
7
8
{
prop: '',
label: '操作',
width: '150',
fixed: 'right',
slotName: 'operation',
slotHeader: 'operateHeader'
}

记的属性过多,插槽名虽然是自定义的,但是如果这个表格非常多的插槽的时候,会不会变成插槽灾难区呢?

Snipaste_2023-10-27_16-31-21.png
像上面这种没注释的,你根本不知道那个插槽是哪个,还不如 el-table-column 那种形式查看的实在。

2、需要兼容多个 el-tabel-column 的属性

我们都知道,封装兼容东西的大部分做法是,直接将他所有的功能都做了,你需要什么就自己配什么。比如我在封装这个组件的时候,基本是遇到一个问题,发现组件没有兼容,然后就需要在里面加,遇到一个加一个。其实这个里面就有心智成本,说到底这样的通用组件,避免不了有些功能没兼容的情况,你自己封装的人会很清楚,哪个属性干什么,但是对用第一次使用的人来说,他根本不知道你这个组件的功能实现需要哪个属性!然后就是去看一遍你的代码,更有甚至缺失的功能要他自己手动添加!

函数式组件的使用

基于上面的问题,我就在想,能不能不修改原来那么多 el-table-column,直接就渲染他们出来,我只需要知道,哪个需要渲染而哪个不需要渲染就行了,基于这个原因,我想到了 Vue 提供的渲染函数:**函数式组件**。
可以参考这两个地址:Vue2 函数式组件 函数式组件语法。那么下面展示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const Table = {
functional: true,
name: "wRenderTable",
props: {
selectionChange: {
type: Function,
default() {
return {};
},
},
tableColumn: {
type: Array,
default() {
return [];
},
},
},
render(h, context) {
const {
props,
data: { attrs },
} = context;
const cellStyle = { height: "76px" };
const headerCellStyle = {
height: "56px",
backgroundColor: "#FCF2F1",
color: "#573E41",
textAlign: "center",
};

const column = props.tableColumn.map((item) => item.label);
return (
<el-table
vLoading={attrs.loading}
{...{ attrs }}
cell-style={cellStyle}
header-cell-style={headerCellStyle}
border
row-key="id"
vOn:selection-change={props.selectionChange}
>
{context.children.map((child) => {
const { propsData } = child.componentOptions;
if (!propsData.type) {
/* 无数据格式化 */
propsData.formatter = (row, column, cellValue) => {
if (!cellValue && cellValue !== 0) {
return "--";
} else {
return cellValue;
}
};
/* 默认居中 */
!propsData.align && (propsData.align = "center");
}
/*propsData.type是选择框时才有。 */
if (column.includes(propsData.label) || propsData.type) {
return child;
} else {
return null;
}
})}
</el-table>
);
},
};

export default Table;

**context.children**能获取到函数式组件下面所有的 node 节点,如下图:

Snipaste_2023-10-27_16-57-10.png
那么我如何控制,到底那个列不需要渲染呢。没错,我只需要通过遍历时,从传进来的 tableColumn 中判断子节点是否存在,来判断是否渲染,不渲染的时候时候,返回 null 即可。

优点

  1. 最大的优点就是,我不需要删除原来一大堆的 el-table-column,又去写一个繁杂麻烦的对象数组。
  2. 使用者没有多少心智负担,比如列展示的组件会处理最终展示的列,只需要将这个列数组传给表格组件,通过判断即可处理渲染不渲染的逻辑。(为什么需要判断呢,因为获取的节点是全部的,只能内部判断那个节点需要渲染,可以看上面的判断逻辑,很简单)
  3. 最重要的是,由于可以使用原来 el-talbel-column,就不需要配置在组件内部配置一大堆属性,而且插槽也不需要自己去定义名字,也不会出现一大堆插槽,不知道谁是谁的局面。
  4. 还有一个必须需要注意的点就是,**函数式组件是无状态的,也没有实例的(this),这个意味着什么,我一开始也不知道,直到我碰到那个问题——我怎么知道进来 tableColumn 是不是最新的。我找了很久都不知道这个怎么去解决,函数式组件也没有 watch 方法。无状态,所以非常依赖外部状态,当 props 里面的属性,更新的时候,这个函数式组件也会重新渲染,那么不就意味着,我根本不需要管 tableColumn 的值,因为它自动会重新熏染。**

最后的吹牛

感觉组件封装就是这样的,先是找到一个可以实现功能的方案,然后就是实现功能的过程中,发现这个方案非常的麻烦,然后就开始思考能不能最小化干扰原来的东西,也能修改。这大概就是我最终选用函数式组件的原因。觉得有用的兄弟够我点个赞或者是收藏,呆在 3 级好久了,谢谢兄弟们了(砰砰砰)