前言
父子组件之间的通信,父组件可通过props向子组件传递数据,也支持通过插槽内容向子组件传入自定义模板内容。
插槽包含两部分内容:(1)插槽内容,由父组件定义;(2)插槽出口,由子组件通过 标签实现。
-
插槽内容:由包含v-slot指令的template标签包裹;
- v-slot指定格式:
v-slot:插槽名称="scope"
,例如v-slot:default="scope"
或解构形式v-slot:default="{row,$index}"
; - v-slot指令简写形式为# :即
<template v-slot:default>...</template>
可简写为<template #default>...</template>
;
- v-slot指定格式:
-
插槽出口:通过slot标签输出
- 具名插槽的输出需要指定name(默认为default,默认插槽可省略name);
- 传递数据:可使用v-bind指令或其简写形式;
-
获取父组件传入的插槽对象:
- 组合式 API:
setup(props, context){console.log('父组件传入的插槽对象', context.slots)}
; - 钩子:
import {useSlots} from "vue";console.log('父组件传入的插槽对象', useSlots())
;
- 组合式 API:
默认插槽
<!-- 默认插槽 -->
<el-button>新增</el-button>
<!-- 提供默认名称的默认插槽 -->
<el-button>
<template v-slot:default>新增</template>
</el-button>
具名插槽
<!-- 具名插槽 -->
<!-- 父组件定义插槽内容 -->
<StandardTable>
<!-- 默认插槽的名称是default,可省略名字 -->
<template v-slot:default>
<el-button type="text">编辑</el-button>
<el-button type="text">删除</el-button>
</template>
<!-- 具名插槽(如果有多个插槽就要定名字了)并接收数据 -->
<template v-slot:score_xian="scope">
<span>{{scope.row.score_xian}}</span>
</template>
</StandardTable>
场景分析
二次封装el-table组件为standard-table,接收tableData,其类型如下:
export type tableDataProps = {
columns: columnProps[], // 列信息
list: object[], // 数据列表
pageData?: pageDataProps, // 分页数据
loading?: boolean, // 加载状态
}
针对列el-table-columns,做了一些定制化配置:
其代码结构如下:
TestStandardTable
调用组件StandardTable,并提供自定义列模板
<StandardTable :tableData="tableData">
<!-- 提供插槽内容, 具名插槽, 自定义列模板 -->
<template #score_xian="{row}">
<span>{{row.score_xian}}</span>
</template>
<template #default="{row}">
<el-button type="text">编辑</el-button>
<el-button type="text" @click="execRemove(row)">删除</el-button>
</template>
</StandardTable>
<script setup lang="ts">
import useTable from '@/hooks/useTable'
const {tableData} = useTable({list:[]})
tableData.columns = [
{ id:4, label:"2023年理科一批分数线", prop:"creFile", minWidth:"120px", multiHeader:true, list:[
{ id:42, label:"河南", prop:"score_henan", minWidth:"180px", multiHeader:true, list:[
{ id:42, label:"郑州", prop:"score_henan", minWidth:"180px", },
{ id:42, label:"洛阳", prop:"score_henan", minWidth:"180px", },
]},
{ id:42, label:"浙江", prop:"score_zhejiang", minWidth:"180px", },
{ id:42, label:"安徽", prop:"score_anhui", minWidth:"180px", },
{ id:41, label:"西安", prop:"score_xian", minWidth:"180px", slot:"score_xian"},
]},
{ id:6, label:"操作", prop:"", slot:"default", minWidth:"120px", fixed:"right", },
]
tableData.list = [{
id: 1,
score_xian:"443分",
score_anhui:"482分",
score_zhejiang:"488分",
score_henan:"514分",
}]
const execRemove = (row={})=> {
console.log('删除', row)
}
</script>
StandardTable
渲染自定义列,因为支持多级表头,故将自定义列功能抽离出来即组件RenderTableColumn。
<el-table>
<template v-for="(item) in tableData.columns" :key="item">
<RenderTableColumn :data="item" />
</template>
</el-table>
<script setup lang="ts">
import {useSlots} from "vue"
const slots = useSlots()
console.log('插槽', slots) // 打印有两个插槽,score_xian 和 default
</script>
RenderTableColumn
<el-table-column :label="data.label">
<!-- 自定义表头内容(插槽) -->
<template v-if="data.header" #header="{column, $index}">
<slot :name="data.header" v-bind="{column, $index}"></slot>
</template>
<template v-slot="{row, $index}">
<!-- 支持多级表头 -->
<template v-if="data.multiHeader && data.list && data.list.length">
<!-- 渲染列 -->
<RenderTableColumn v-for="childColumn in data.list" :key="childColumn" :data="childColumn" />
</template>
<!-- 自定义列内容渲染, 级别:render>slot>prop>default slot -->
<!-- render -->
<template v-if="data.render && typeof data.render === 'function'">
<span v-html="data.render(row, $index)"></span>
</template>
<!-- 具名插槽 -->
<template v-else-if="data.slot">
<slot :name="data.slot" v-bind="{row, $index}"></slot>
</template>
<!-- prop -->
<template v-else-if="data.prop">{{row[data.prop] || '-'}}</template>
<!-- 默认插槽 -->
<template v-else>
<slot v-bind="{row, $index}"></slot>
</template>
</template>
</el-table-column>
<script setup lang="ts">
import {useSlots} from "vue"
const slots = useSlots()
console.log('插槽', slots) // 打印的是空的,没有任何插槽传入进来
</script>
运行结果:
发现我们在TestStandardTable.vue中定义的两个自定义列模板没有生效,为什么呢?
插槽透传
由于在TestStandardTable.vue向子组件StandardTable传入了自定义列模板(提供了两个插槽)以自定义展示列。按需求,这两个插槽是在el-table-columns接收展示的,但是插槽是由父组件流向子组件的,如何实现父组件给子组件的插槽流向孙组件呢即如何实现插槽透传?
这里笔者想到的解决方案是:借助父子组件插槽流动特性(父组件传递,子组件接收),父传子,子传孙… ,修改上述代码如下。
<!-- StandardTable.vue -->
<el-table>
<template v-for="(item) in tableData.columns" :key="item">
<RenderTableColumn :data="item">
<!-- 插槽透传 -->
<template v-for="itemSlot in Object.keys(slots)" :key="itemSlot" v-slot:[itemSlot]="temp">
<slot :name="itemSlot" v-bind="temp"></slot>
</template>
</RenderTableColumn>
</template>
</el-table>
<!-- RenderTableColumn.vue -->
<el-table-column :label="data.label">
<!-- 自定义表头内容(插槽) -->
<template v-slot="{row, $index}">
<!-- 支持多级表头 -->
<template v-if="data.multiHeader && data.list && data.list.length">
<RenderTableColumn v-for="childColumn in data.list" :key="childColumn" :data="childColumn">
<!-- 插槽透传 -->
<template v-for="itemSlot in Object.keys(slots)" :key="itemSlot" v-slot:[itemSlot]="temp">
<slot :name="itemSlot" v-bind="temp"></slot>
</template>
</RenderTableColumn>
</template>
<!-- 省略 -->
</template>
</el-table-column>
再次运行:
OK,搞定!现在,需求是实现了,但是是通过父子、子孙等的传递,这样有个弊端:如果插槽内容的逻辑复杂,这种击鼓传花的方式会带来过多的损耗。
如果你有更好的方式,请留言~
关键代码
<RenderTableColumn :data="item">
<!-- 插槽透传 -->
<template v-for="itemSlot in Object.keys(slots)" :key="itemSlot" v-slot:[itemSlot]="temp">
<slot :name="itemSlot" v-bind="temp"></slot>
</template>
</RenderTableColumn>
<script setup lang="ts">
import {useSlots} from "vue"
const slots = useSlots()
</script>
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END