1. 初步了解插槽
什么是插槽?
为什么要使用插槽?
在组件通信中(详情见文章:Vue组件学习、组件通信),我们了解到组件之间如何传递数据。那父组件在复用子组件的时候除了想给子组件传递数据,还想更改子组件的内容和样式时,怎么办呢?
一般来说,父组件是没办法更改子组件的内容和样式的,总结就是“你可以用我但是不可以更改我”。
就像买车一样 不能自己去决定车的配置、外观、性能、尺寸,这些都是厂家自己设计好然后批量生产的 ,大家买下都是一样的。
插槽的作用:
让父组件向子组件指定位置插入html结构,也是一种组件间的通信方式。
2. 插槽的分类
2.1 默认插槽
案例场景:以下是一个组件的复用(一个蓝色的div即是一个组件),该组件在页面中复用了3次,我想对某一个组件实现私人定制,比如,美食组件我只想放一张图片,电影组件中我想放一个视频,如何做?
由上方效果变成下面这样的:
制定子组件Category.vue:
这是没有使用插槽的情况,组件不能实现定制化:
<template>
<div class="category">
<h3>{{title}}</h3>
<ul>
<li v-for="(item,index) in listData" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default{
name:"Category",
// 接收父组件传过来的值
props:['listData','title']
}
</script>
<style>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
</style>
在App.vue中使用Category.vue:
<template>
<div id="app">
<!-- 复用子组件 静态传输标题,动态传输数组-->
<Category title="美食" :listData="foods" />
<Category title="书籍" :listData="books"/>
<Category title="电影" :listData="films"/>
</div>
</template>
<script scope="this api replaced by slot-scope in 2.5.0+">
import Category from './components/Category.vue';
export default {
name: 'App',
components: {
Category,
},
data(){
return{
// 定义数据
foods:['火锅','重庆火锅','小龙虾','牛排'],
books:['语文','数学','英语','化学'],
films:['重庆森林','七龙珠','犬夜叉','心灵捕手'],
}
}
}
</script>
<style>
#app {
display: flex;
justify-content: space-around;
}
</style>
以上复用只能出现第一个页面效果,见下图:
如果不使用作用域插槽,仅在App.vue更改组件中内容,子组件是不会将内容渲染的
在App.vue中更改子组件内容,此时子组件中无插槽!!!:
<Category title="美食" >
<img src="@/assets/hotpot.jpg" alt="">
</Category>
<Category title="书籍" :listData="books"/>
<Category title="电影" :listData="films"/>
子组件无法渲染在父组件中更改的页面结构,如下图:
下面使用默认插槽:
在子组件Category.vue中定义一个默认插槽:
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 定义一个插槽 组件的页面结构完全交给父组件去定义-->
<slot>默认值,当父组件没有出现时,我会出现</slot>
</div>
</template>
<script>
export default{
name:"Category",
// 接收父组件传过来的值
props:['title']
}
</script>
<style>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
</style>
App.vue:
<template>
<div id="app">
<!-- 复用子组件 静态传输标题,动态传输数据 -->
<Category title="美食" >
<img src="@/assets/hotpot.jpg" alt="">
</Category>
<!-- <Category title="书籍" :listData="books"/> -->
<Category title="书籍">
<ul>
<li v-for="(item,index) in books" :key="index">{{item}}</li>
</ul>
</Category>
<!-- <Category title="电影" :listData="films"/> -->
<Category title="电影">
<video src="@/assets/1.mp4" controls></video>
</Category>
</div>
</template>
<script scope="this api replaced by slot-scope in 2.5.0+">
import Category from './components/Category.vue';
export default {
name: 'App',
components: {
Category,
},
data(){
return{
// 定义数据
foods:['火锅','重庆火锅','小龙虾','牛排'],
books:['语文','数学','英语','化学'],
films:['重庆森林','七龙珠','犬夜叉','心灵捕手'],
}
}
}
</script>
<style>
#app {
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
注意:对样式的定义,可以定义在父组件也可以定义在子组件。
区别:
- 定义在父组件,结构先编译好样式再传给子组件;
- 定义在子组件中,结构传给子组件,再由子组件编译。
<style>
img{
width: 100%;
}
video{
width: 100%;
}
</style>
2.2 具名插槽
上诉案例中,提高需求,我想在分割线下添加一个跳转链接(分割线以上的都是默认插槽区域),如何做?
使用默认插槽和使用作用域插槽的区别:
Category.vue
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 定义一个插槽 -->
<slot>默认值,当父组件没有出现时,我会出现</slot>
-------------------------
<!-- 声明一个具名插槽 -->
<slot name="link"></slot>
<!-- <ul>
<li v-for="(item,index) in listData" :key="index">{{item}}</li>
</ul> -->
</div>
</template>
App.vue
<template>
<div id="app">
<!-- 复用子组件 静态传输标题,动态传输数据 -->
<Category title="美食" >
<img src="@/assets/hotpot.jpg" alt="">
<!-- 将元素插入指定名字插槽内 -->
<!-- vue对具名插槽的新写法 简写形式:#插槽名 -->
<template >
<div class="foot"><a href="">更多美食</a></div>
</template>
</Category>
<!-- <Category title="书籍" :listData="books"/> -->
<Category title="书籍">
<ul>
<li v-for="(item,index) in books" :key="index">{{item}}</li>
</ul>
<!-- 将元素插入指定名字插槽内 -->
<!-- 传统用法:slot="插槽名" -->
<div class="foot" slot="link" >
<a href="https://www.taobao.com/" >去淘宝</a>
<a href="https://www.taobao.com/" >去京东</a>
</div>
<br>
<!--<a href="https://www.taobao.com/">去淘宝</a>
<br>
<a href="https://www.taobao.com/">去淘宝</a> -->
</Category>
<!-- <Category title="电影" :listData="films"/> -->
<Category title="电影">
<video src="@/assets/1.mp4" controls></video>
<!-- 将元素插入指定名字插槽内-->
<!-- vue对具名插槽的新写法,v-slot:插槽名-->
<template v-slot:link>
<div class="foot">
<a href="">经典</a>
<a href="">热门</a>
<a href="">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
</div>
</template>
<script scope="this api replaced by slot-scope in 2.5.0+">
import Category from './components/Category.vue';
export default {
name: 'App',
components: {
Category,
},
data(){
return{
// 定义数据
foods:['火锅','重庆火锅','小龙虾','牛排'],
books:['语文','数学','英语','化学'],
films:['重庆森林','七龙珠','犬夜叉','心灵捕手'],
}
}
}
</script>
<style >
#app,.foot{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
h4{
text-align: center;
}
</style>
2.3 作用域插槽
理解: 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
案例:
父组件:
<Category title="游戏">
<!-- 一定要使用template模板编译 -->
<template scope="row">
<!-- {{row.games}} -->
<ul>
<li v-for="(g, index) in row.games" :key="index">{{ g }}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<!-- 一定要使用template模板编译 -->
<template scope="row">
<ol>
<li style="color: red" v-for="(g, index) in row.games" :key="index">
{{ g }}
</li>
</ol>
</template>
</Category>
<Category title="游戏">
<!-- 一定要使用template模板编译 -->
<template scope="row">
<h4 v-for="(g, index) in row.games" :key="index">{{ g }}</h4>
</template>
</Category>
子组件中:
<h3>{{title}}分类</h3>
<!-- 定义一个作用域插槽 -->
<slot :games='games'>默认内容</slot>
<script>
export default {
props: ['title'],
data() {
return {
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽']
}
}
}
</script>
作用域插槽可以看成数据的方向流动,有子组件传给父组件
注意:作用域插槽的使用一定要使用template模板