需求分析
后台管理系统中,使用的大量的table组件,而el-table的使用都是写在template中(如下),这样不利于一些公共业务组件和逻辑的提取。围观别的组件库如(ant-design-vue、iviews)的table组件,都是在js中定义,通过属性(columns)传递来实现。我们将对el-table进行封装以实现类似ant-desingn-vue中的使用效果。在封装的过程中,我们需要注意一下几点:
- 保留原el-table,el-table-column的属性和方法
- 多表头,自定义内容,自定义表头等功能保留
<el-table :data="data">
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
</el-table>
现在网上的做法基本都是传递一个属性(columns),然后用v-for遍历,对于自定义内容,再写v-if v-else处理(如下)。
<el-table :data="data">
<el-table-column
v-for="item in columns"
:key="item.prop"
:label="item.label"
:prop="item.prop">
</el-table-column>
</el-table>
在业务比较简单的情况话,这种处理还能使用,但是当需求中出现 透传组件属性 、监听原组件事件、 自定义内容、自定义头部、多表头等复杂情况的时候,这种方式实现就比较困难了。
思考:
-
透传组件属性(props)、监听组件事件(events)。 使用props和emits ????
特别繁琐
最佳选择 高阶属性 $attrs
-
自定义内容,自定义头部 在组件中slot进去 ????
不利于封装
最佳选择 传递一个jsx函数
-
多表头 递归组件???
最佳选择 递归函数
总结上面我们提到的3个思考,我们来看一下具体怎么实现
具体实现
<template>
<render/>
</template>
<script lang="jsx" setup>
import {
useAttrs, computed, ref
} from 'vue';
let key = 0;
const props = defineProps({
data: Array,
columns: Array,
});
const attrs = useAttrs();
// 处理多表头的情况
const getTableColumn = (column) => {
const {
render,
headerRender,
children,
...data
} = column;
// 处理自定义头部和自定义内容
const slots = {};
if (headerRender) {
slots.header = headerRender;
}
if (render) {
slots.default = render;
}
if (children) {
slots.default = () => children.map(getTableColumn);
}
if (!column.prop && !column.type) {
key += 1;
}
const columnProps = { align: 'center', ...data };
return (
<el-table-column
key={column.prop || column.type || key}
{...columnProps}
v-slots={slots}>
</el-table-column>
);
};
const tableColumns = computed(() => props.columns.map(getTableColumn));
// 暴露模板ref,以便能在外部调用el-table的method
const tableRef = ref(null);
defineExpose({
tableRef
})
// setup script 中使用 jsx(打包有问题)
// const inst = getCurrentInstance();
// inst.render = () => (<el-table
// ref={tableRef}
// data={props.data}
// {...attrs}>
// {tableColumns.value}
//</el-table>);
const render = ()=>(<el-table
ref={tableRef}
data={props.data}
{...attrs}>
{tableColumns.value}
</el-table>)
</script>
具体使用
<template>
<CustomTable
ref="el"
:data="data"
:columns="tableColumns"
@selection-change="handleSelect">
</CustomTable>
</template>
<script lang="jsx" setup>
import { reactive, defineProps, inject, ref } from 'vue';
import CustomTable from './custom-table.vue';
const data = reactive([
{ name: 'ceshi1', child1: 'child1', child2: 'child2' },
{ name: 'ceshi2', child1: 'child1', child2: 'child2' },
{ name: 'ceshi3', child1: 'child1', child2: 'child2' },
]);
const el = ref(null);
const tableColumns = reactive([
{ type: 'selection' },
{ label: '姓名', prop: 'name' },
{
label: '子集',
prop: 'child',
children: [
{
label: '子集1',
prop: 'child1',
},
{
label: '子集2',
prop: 'child2',
},
],
},
{
label: '编辑',
prop: 'edit',
headerRender: () => <el-button>测试</el-button>,
render: ({ row }) => <el-button onClick={() => handleClick(row)}>123</el-button>,
},
]);
function handleClick(row) {
data.push(
{ name: 'ceshi4', child1: 'child1', child2: 'child2' },
);
// 取消选中
el.value.tableRef.clearSelection();
}
function handleSelect(selection) {
console.log(selection);
}
</script>
最终效果
使用
添加 columns 属性 对象数组(column)
el-table 的属性(props)、事件(events)可以直接使用
el-table 的方法(methods) 需要获取组件ref的 tableRef
columns
| 参数 | 类型 | 说明 |
|---|---|---|
| children | object[] | 多级表头 |
| render | (scope)=> jsx | 自定义内容 |
| headerRender | (scope)=>jsx | 自定义头部 |
el-table-column 上的参数可以直接在column中填写
提升
使用过react的同学,都了解高阶组件,有没有感觉上面的custom-table有点高阶组件的影子,那vue为啥很少有人提高阶组件这个概念呢?vue的高阶组件怎么实现呢?
有兴趣大家可以看一下这个文章:github.com/HcySunYang/…