使用 TSX 对 el-table 表格组件的封装实践
背景
最近在准备新项目,公司之前的项目主要使用 vue2 + element ui,利用这次机会决定尝试一下新的技术栈,于是从零搭建了一个 vue3 + vite + ts + element plus 的项目模板,方便快速开始新的项目。
如果你对项目感兴趣,欢迎关注我的 GitHub 仓库 Ares-admin 。如果有任何问题或建议,请随时联系我。
我在公司主要参与中后台项目的开发,经常需要处理报表数据,并频繁使用表格组件。然而 element ui 中表格组件在使用上不够方便,为了能够提高开发效率,并有更多时间划水,就想对表格组件进行一些封装。
需求设计
在之前的 vue2 项目中,我根据业务对 el-table
组件进行了一些封装尝试,来提升开发体验和效率。但是由于经验不足,导致一些地方设计不合理。这次我认真思考和调研相关内容后,总结了一些常见的表格封装设计方案:
- 通过
schema
来描述表格的列信息 - 将查询表单、分页组件、操作按钮封装到表格组件内
- 为表格组件增加一些业务逻辑
通过定义 columns
属性来配置表格列信息,可以让表格结构更加清晰,易于扩展,使用起来也更方便,之前的老项目也采用类似的封装方案。
ant design
等主流组件库都采用了类似的设计思想。
不过,将表单和业务操作封装到表格组件内,我个人不是特别认同,虽然可以提高一定的开发效率,但也带来了一些问题和挑战。这种方式增加了组件的复杂度和使用成本,维护起来也会更加困难。
另外,这种设计缺乏灵活性,难以根据不同场景进行调整和扩展。当需要在不同的页面或场景下使用不同的表单或操作逻辑时,由于这些逻辑已经封装在表格组件内部,就无法轻松地进行调整和定制。这可能会导致需要增加额外的代码来满足不同的需求,增加了维护成本。
如果前期设计不好,后期可能会有一堆坑。
综合这些考虑,最终采用以下封装方案:
- 将表格列信息通过
columns
进行配置,使表格结构更清晰、使用更方便 - 使用 TSX 封装组件,方便代码复用,提升开发体验
- 只保留分页器逻辑在表格组件内
其他功能组件和逻辑如果有需要,可以通过组合的方式进行使用。考虑到业务中需要自定义列的功能,我将自定义列逻辑也封装到表格组件中了。
代码实现
- 定义
columns
参数 | 说明 | 类型 |
---|---|---|
需要继承 el-table-column 所有属性 | ||
hidden | 是否隐藏列 | boolean |
children | 多级表头 | Array |
能根据需要灵活扩展 ... |
- 事件处理
事件名 | 说明 | 回调参数 |
---|---|---|
需要继承 el-table 所有事件 | ||
change | 分页排序回调 | pageSize, pageNum, prop, order, type |
column-change | 自定义列回调 | columns |
能根据需要灵活扩展 ... |
基本结构
function render() {
return (
<div class="x-table">
<ElTable
{...tableProps}
v-slots={extraSlots}
>
{
props.columns.map((item) => {
if (Array.isArray(item.children)) {
return renderColumnChildren(item, item.children)
}
return renderTableColumn(item)
})
}
</ElTable>
{showPagination.value && renderPagination()}
{!isUndefined(props.visibleColumn) && renderCustomColumn()}
</div>
)
}
表格列渲染
function renderTableColumn(column: XTableColumn) {
const columnSlots: {
default?: (scope: Record<string, any>) => any
header?: (scope: Record<string, any>) => any
} = {}
const slot = getSlot(column)
const headerSlot = getSlot(column, 'header')
if (slot) {
columnSlots.default = scope => slot(scope)
}
if (headerSlot) {
columnSlots.header = scope => headerSlot(scope)
}
return (
<ElTableColumn {...getColumnProps(column)} >
{columnSlots}
</ElTableColumn>
)
}
自定义列渲染
function renderCustomColumn() {
const customColumnProps = {
columns: props.columns,
visible: props.visibleColumn,
onChange: handleColumnChange,
onVisibleChange: handleVisibleChange,
}
return (
<XColumn {...customColumnProps} />
)
}
使用示例
const columns = [
{
type: 'selection',
},
{
label: '日期',
prop: 'date',
align: 'left',
sortable: 'custom',
formatter(row) {
return dayjs(row.date).format('YYYY-MM-DD')
},
},
{
label: '地址',
prop: 'address',
hidden: true,
children: [
{
label: '区域',
prop: 'area',
},
],
},
{
label: '操作',
prop: 'action',
fixed: 'right',
},
]
<x-table
v-model:visible-column="dialogVisible"
:columns="columns"
:data-source="tableData"
:total="tableTotal"
:page-size="form.pageSize"
:page-num="form.pageNum"
@change="handleTableChange"
@column-change="handleColumnChange"
@selection-change="handleSelectionChange"
@header-dragend="handleHeaderDragend"
>
<template #name="{ row }">
<el-button>{{ row.name }}</el-button>
</template>
<template #name-header>
<span>表头插槽</span>
</template>
<template #action>
<el-button type="primary">新增</el-button>
<el-button>修改</el-button>
</template>
</x-table>
// 修改自定义列
function handleColumnChange(cols: XTableColumn[]) {
columns.value = cols
}
// 表格多选
function handleSelectionChange(value: XTableData[]) {
console.log('selection change =>',value)
}
// 分页和排序
function handleTableChange(data: XTableChangeData) {
const { pageSize, pageNum, sort, prop, type } = data
state.pageNum = pageNum
state.pageSize = pageSize
state.sort = sort
state.prop = prop
}
完整的代码可以在我的 GitHub 仓库 Ares-admin 中查看。
总结
本文分享了一些关于如何封装 el-table 组件的思路和简单功能实现。在工作中通过总结和思考,我们可以封装一些业务组件来提高开发效率,减少重复的代码编写。在封装组件时,应该根据具体业务需求和团队特点,选择合适的封装方式。
最后,欢迎大家在评论区讨论学习,分享自己的经验和更好的想法,帮助我们一起成长。
转载自:https://juejin.cn/post/7252171148757581879