vue3击鼓传花式实现插槽透传

前言

父子组件之间的通信,父组件可通过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>
  • 插槽出口:通过slot标签输出

    • 具名插槽的输出需要指定name(默认为default,默认插槽可省略name);
    • 传递数据:可使用v-bind指令或其简写形式;
  • 获取父组件传入的插槽对象:

    • 组合式 API:setup(props, context){console.log('父组件传入的插槽对象', context.slots)}
    • 钩子:import {useSlots} from "vue";console.log('父组件传入的插槽对象', useSlots())

默认插槽

<!-- 默认插槽 -->
<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,做了一些定制化配置:
columns.png

其代码结构如下:

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>

运行结果:
slot_1.png

发现我们在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>

再次运行:
slot_2.png

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>

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYLwy6k0' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片