50行代码搞定 ElementPlus el-table二次封装

3,135 阅读3分钟

需求分析

后台管理系统中,使用的大量的table组件,而el-table的使用都是写在template中(如下),这样不利于一些公共业务组件和逻辑的提取。围观别的组件库如(ant-design-vue、iviews)的table组件,都是在js中定义,通过属性(columns)传递来实现。我们将对el-table进行封装以实现类似ant-desingn-vue中的使用效果。在封装的过程中,我们需要注意一下几点:

  1. 保留原el-table,el-table-column的属性和方法
  2. 多表头,自定义内容,自定义表头等功能保留
<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>

在业务比较简单的情况话,这种处理还能使用,但是当需求中出现 透传组件属性监听原组件事件自定义内容自定义头部多表头等复杂情况的时候,这种方式实现就比较困难了。

思考:

  1. 透传组件属性(props)、监听组件事件(events)。 使用props和emits ????

    特别繁琐

    最佳选择 高阶属性 $attrs

  2. 自定义内容,自定义头部 在组件中slot进去 ????

    不利于封装

    最佳选择 传递一个jsx函数

  3. 多表头 递归组件???

    最佳选择 递归函数

总结上面我们提到的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>

最终效果

image.png

使用

添加 columns 属性 对象数组(column)

el-table 的属性(props)、事件(events)可以直接使用

el-table 的方法(methods) 需要获取组件ref的 tableRef

columns

参数类型说明
childrenobject[]多级表头
render(scope)=> jsx自定义内容
headerRender(scope)=>jsx自定义头部

el-table-column 上的参数可以直接在column中填写

提升

使用过react的同学,都了解高阶组件,有没有感觉上面的custom-table有点高阶组件的影子,那vue为啥很少有人提高阶组件这个概念呢?vue的高阶组件怎么实现呢?

    有兴趣大家可以看一下这个文章:github.com/HcySunYang/…