了解插槽之前请先了解vue组件基础及注册
1、vue2插槽介绍
在2.6.0中,具名插槽和作用域插槽引入了一个新的统一语法(v-slot指令)。它将取代slot和slot-scope;
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 < slot > 元素作为承载分发内容的出口。
允许父组件更改子组件模板内容,例如:
//父组件
<navigation-link url="/profile">
Your Profile
</navigation-link>
//子组件NavigationLink.vue
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
//渲染结果
<a href="/profile" class="nav-link">
Your Profile
</a>
如果 < navigation-link > 的 template 中没有包含一个 < slot > 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
/*子组件*/
<template>
<a v-bind:href="url" class="nav-link"></a>
</template>
/*父组件*/
<navigation-link url="/profile">
<span> Your Profile</span>a标签
</navigation-link>
//渲染结果,a标签中间无任何内容被渲染,span以及文本均被抛弃
<a href="/profile" class="nav-link">
</a>
2、vue3插槽介绍
//子组件
<script setup>
</script>
<template>
<button><slot></slot></button>
</template>
//父组件
<script setup>
import {ref} from 'vue';
import MyButton from '@/components/MyButton.vue'
</script>
<template>
<div class="index-main">
<MyButton>点击<span style="display: inline-block; width:10px; height: 10px; border-radius: 50%; background-color: #f00; margin-left: 5px;"></span></MyButton>
-->
</div>
</template>
/*渲染结果*/
<button>点击<sapn data-v-b4e148ca="" style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: rgb(255, 0, 0); margin-left: 5px;"></sapn></button>
插槽内容可以是任意合法的模板内容,不局限于文本;可以传入多个元素,甚至是组件
3、插槽渲染作用域
vue2和vue3插槽作用域须知如下:
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
/*父组件,父组件中的msg属性在父组件和子组件中均可渲染,但在MyButton子组件中无法获取msg的值,只有使用props传值后才可获取到*/
<script setup>
import {ref} from 'vue';
import MyButton from '@/components/MyButton.vue'
const msg = ref('提交');
</script>
<template>
<div class="index-main">
<div>{{msg}}</div>
<MyButton>{{msg}}</MyButton>
</div>
</template>
4、插槽的默认值设置
4.1、Vue2参考插槽——后备内容
/*vue2子组件设置默认值*/
<template>
<a v-bind:href="url" class="nav-link">
<slot>跳转链接</slot>
</a>
</template>
4.2、Vue3参考插槽——默认内容
/*vue3子组件设置默认值*/
<script setup>
</script>
<template>
<button>
<slot>提交</slot>
</button>
</template>
/*vue3 父组件*/
<template>
<div class="index-main">
<!-- 按钮文本为 提交 -->
<MyButton></MyButton>
<!-- 按钮文本为 立即提交 -->
<MyButton>立即提交</MyButton>
</div>
<template>
5、具名插槽
简单来说,当子组件有多个插槽时,为每个插槽设置不同名字,父组件中引用时可根据名字修改不同的插槽内容;
5.1、vue2具名插槽介绍
自 2.6.0 起有所更新。已废弃的使用 slot attribute;已废弃slot属性的用法具体请查看官网;
子组件插槽新语法:< slot>元素,此元素用油一个特殊的属性name,用name来定义额外的插槽;
一个不带 name 的 出口会带有隐含的名字“default”。
/*base-layout子组件*/
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父组件使用时:
可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
注意 v-slot 只能添加在 上;只有一种特殊情况,可暂不了解;
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
/*父组件*/
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<!--
更明确的写法
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
-->
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
新语法与vue3相同,具体示例不再冗余展示;具体示例请参照vue3;将对应的props传值修改为vue2写法即可;
5.2、Vue3具名插槽介绍
子组件包含多个插槽时:
< slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID;
这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 出口会隐式地命名为“default”
父组件的使用介绍:
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 < template> 元素,并将目标插槽的名字传给该指令;
v-slot 有对应的简写 #,因此 < template v-slot:left> 可以简写为 <template #left>
示例参考:
/*vue3 定义一个列表组件ListChild.vue,左侧图标,右侧箭头*/
<template>
<div class="list-bar" title="标题">
<div v-if="leftShow" class="list-left">
<slot name="left">
<div class="list-leftIcon"></div>
</slot>
</div>
<div class="list-middle">
<slot>列表内容</slot>
</div>
<div v-if="rightShow" class="list-rightIcon">
<slot name="right">></slot>
</div>
</div>
</template>
<script setup>
const props = defineProps({
leftShow:{
type: Boolean,
default: true
},
rightShow:{
type: Boolean,
default: true
}
});
</script>
<style scoped>
.list-bar{
display: flex;
display: -webkit-flex;
align-items: center;
-webkit-align-items: center;
}
.list-left{
margin-right: 8px;
}
.list-leftIcon{
width: 4px;
height: 4px;
background: #f00;
}
.list-rightIcon{
margin-left: 10px;
}
</style>
/*父组件*/
<script setup>
import ListChild from '@/components/ListChild.vue'
</script>
<template>
<div class="index-main">
<!-- 使用默认效果 -->
<ListChild></ListChild>
<!-- 修改右侧内容 -->
<ListChild>
<template v-slot:right>
<!-- v-sole冒号后面为插槽的name -->
<!-- 中间为重新定义的左侧内容 -->
<span style="color: #999; font-size: 12px;">2023-01-01</span>
</template>
</ListChild>
<!-- 修改左侧及主体内容 -->
<ListChild>
<template #left>
<span style="color: #999; font-size: 12px;">2023-01-01</span>
</template>
<!-- 默认不带name的均为默认插槽内容,是否 -->
<!-- <div>主体内容修改</div> -->
<template #default>主体内容</template>
</ListChild>
</div>
</template>
展示效果如下:
5.3、具名插槽内容替换图解
6、Vue3条件插槽
当需要根据插槽是否存在来判断某些内容时,可以结合$slots 属性与 v-if 来实现。(vue3独有特点)
在下面的示例中,我们定义了一个卡片组件,它拥有三个条件插槽:header、footer 和 default。 当 header、footer 或 default 存在时,我们希望包装它们以提供额外的样式:
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div v-if="$slots.default" class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
7、作用域插槽
备注:自 2.6.0 起有所更新。Vue2已废弃的使用 slot-scope;vue2和vue3的语法一致;但下面的示例为VUE3的组合式写法;
在插槽的渲染作用域中我们提到,插槽的内容无法访问到子组件的状态;
但是某些场景下可能需要父组件域内和子组件域内均能访问到此数据;此时我们需要让子组件渲染时将部分数据提供给插槽,以便父组件可以获取到;
7.1、默认插槽作用域插槽的设置
实现方法:将子组件的插槽设置一个属性;
如下面ListChild.vue子组件中slot标签上的count和list属性;也可直接使用v-bind绑定对象
父组件中写法:
v-slot=“slotProps”
v-slot:default=“slotProps”
缩写:
#default=“slotProps”
解构插槽:
v-slot=“{子组件属性名}”
/*子组件ListChild.vue*/
<template>
<div class="list-bar">
<slot :count="1" :list="listVal" v-bind="{a:1,b:2}">列表内容</slot>
</div>
</template>
<script setup>
import { ref } from 'vue';
const listVal = ref('列表主题内容');
</script>
/*父组件index.vue*/
<script setup>
import { ref } from 'vue';
</script>
<template>
<div class="index-main">
<!--
slotProps可自定义名称
slotProps的值为子组件slot标签上的所有自定义属性的对象合集{ "count": 1, "list": "列表", "a": 1, "b": 2 }
-->
<ListChild v-slot="slotProps">
{{slotProps.count}}--{{slotProps.list}}--{{slotProps.a}}--{{slotProps.b}}
</ListChild>
<!-- 使用解构赋值 -->
<ListChild v-slot="{count,list,a,b}">
{{count}}--{{list}}--{{a}}--{{b}}
</ListChild>
<!-- 使用解构赋值,属性名重命名 -->
<ListChild v-slot="{count:countCopy ,list}">
{{countCopy }}--{{list}}
</ListChild>
</div>
</template>
7.2、具名作用域插槽
具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到;
父组件中写法:
v-slot:name=“slotProps”
缩写:
#name=“slotProps”
注意插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 slotProps 的结果是 { “count”: 1, “list”: “列表主题内容” }
/*子组件ListChild.vue*/
<template>
<div class="list-bar">
<slot name="label" :count="1" :list="listVal">列表内容</slot>
</div>
</template>
<script setup>
import { ref } from 'vue';
const listVal = ref('列表主题内容');
</script>
/*父组件index.vue*/
<ListChild v-slot:label="slotProps">
{{slotProps}}
</ListChild>
注意要点:
- 当只有一个默认插槽时,可以把v-slot直接用在组件上;
- 默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:
- 只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法;
8、动态插槽名
动态指令参数在 v-slot 上也是有效的;也可简写;具体使用方法如下
/*子组件*/
<template>
<div class="list-bar" title="标题">
<div v-if="leftShow" class="list-left">
<slot name="left">
<div class="list-leftIcon"></div>
</slot>
</div>
<div class="list-middle">
<slot>列表内容</slot>
</div>
<div v-if="rightShow" class="list-rightIcon">
<slot name="right">></slot>
</div>
</div>
</template>
/*父组件index.vue*/
<script setup>
import {ref} from 'vue';
const dynamicSlotName = ref('left');
setTimeout(()=>{
dynamicSlotName.value = 'right';
},1000)
</script>
<template>
<div>
<ListChild :listData="listData" prop="title">
<template v-slot:[dynamicSlotName]>1111</template>
</ListChild>
</div>
</template>
vue2官方文档未提及缩写写法,建议采用v-slot的写法定义动态插槽名;
Vue3可使用如下缩写写法;
/*缩写写法*/
<template #[dynamicSlotName]>1111</template>