Vue中的插槽作用域:如何设计一个灵活的表格组件?
做后台管理系统,表格几乎是绕不过去的组件。它看起来很普通:给数据、给列配置,渲染行和列。但真封起来就会发现,产品经理总能提出新需求:状态列要显示不同颜色的标签、操作列要放三四个按钮、姓名列点击要跳详情、金额列要格式化、空数据时要引导新建。如果全都写进表格组件里,它会迅速变成业务逻辑垃圾场;如果完全交给父组件,使用成本又太高。
Vue 的作用域插槽,恰好是这两者之间的平衡点:表格组件负责数据遍历、列布局、基础样式和交互骨架,父组件负责决定某个单元格到底长什么样。子组件通过插槽把 row、column、value、index 等上下文传出去,父组件按需接收。这就是设计灵活表格组件的核心思路。
表格组件为什么容易“越封越死”
很多表格封装一开始都是这样的:传入 `columns`,每列配置 `formatter` 或 `render` 函数。简单场景没问题,但一旦遇到复杂单元格,就会变成字符串拼接、`v-html`、或者一长串 `if-else`。更麻烦的是,这些渲染逻辑写在 JS 配置里,脱离了模板,可读性和维护性都会下降。
另一种极端是表格组件只负责渲染 `<table>`,所有单元格都让父组件自己写。灵活是灵活了,但每个页面都要重复处理表头、空状态、加载态、行 key、对齐方式,最后封装等于没封装。
比较合理的边界是:通用结构留在表格组件,差异化 UI 交给作用域插槽。
作用域插槽:把决定权还回去
表格组件在渲染每个单元格时,其实知道当前行、当前列、当前值、索引。它不需要知道这个值应该渲染成标签、链接还是按钮,只需要把这些信息通过插槽暴露出去。
比如父组件这样使用:
<FlexTable :data="rows" :columns="columns" row-key="id">
<template #cell-name="{ row }">
<a href="javascript:;" @click="goDetail(row)">{{ row.name }}</a>
</template>
<template #cell-status="{ value }">
<span :class="['tag', `tag-${value}`]">
{{ statusMap[value] }}
</span>
</template>
<template #cell-actions="{ row }">
<button @click="edit(row)">编辑</button>
<button @click="remove(row)">删除</button>
</template>
</FlexTable>
父组件只关心“这个单元格怎么展示”,表格组件继续关心“行怎么排、列怎么分布、空状态怎么显示”。职责清晰,扩展点也足够。
一个最小可用的 FlexTable
子组件可以这样写:
```vue
<script setup>
const props = defineProps({
data: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
rowKey: { type: [String, Function], default: 'id' },
loading: Boolean,
emptyText: { type: String, default: '暂无数据' }
})
const getRowKey = (row, index) => {
if (typeof props.rowKey === 'function') return props.rowKey(row)
return row[props.rowKey] ?? index
}
</script>
<template>
<div class="flex-table">
<table>
<thead>
<tr>
<th
v-for="col in columns"
:key="col.key"
:style="{ width: col.width, textAlign: col.align }"
<slot :name="`header-${col.key}`" :column="col">
{{ col.title }}
</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td :colspan="columns.length">加载中...</td
转载请注明出处,版权归原作者所有。
管理员
黑卡会员