文章目录
引例
想要实现如下效果:
发现3个模块的样式一样,所以可以写一个组件,传递不同的数据实现。
实现代码:
单个样式:Category组件(子组件)
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: 'CategoryVue',
props:['listData','title']
}
</script>
<style>
.category{
width:200px;
height: 300px;
background: lightseagreen;
}
h3{
text-align: center;
background: lightgoldenrodyellow;
}
</style>
App.vue:
<template>
<div class="container">
<Category title="美食" :listData="foods"/>
<Category title="游戏" :listData="games"/>
<Category title="电影" :listData="films"/>
</div>
</template>
<script>
import Category from "@/components/Category";
export default {
name: "App",
data() {
return {
foods: ['烧烤', '火锅', '小龙虾', '螺蛳粉'],
films: ['《当幸福来敲门》', '《泰坦尼克号》', '《阿甘正传》', '《海上钢琴师》'],
games:['王者荣耀','我的世界','和平精英','穿越火线']
}
},
components: {
Category
}
}
</script>
<style lang="css">
.container{
display: flex;
justify-content: space-around;
}
</style>
那如果想要实现如下效果又该怎么做呢?
(即不同的模块显示的内容格式不一样)——可以通过插槽来实现
普通插槽
插槽标签:<slot></slot>
使用方法
- 插槽一般定义在子组件中,相当于一个占位。
<slot></slot>
- 父组件模板中的子组件标签要写成一对的样式,即:
<Category></Category>
- 这样
<Category></Category>
中的内容就会显示在子组件<slot></slot>
标签的位置。 - 如果
<Category></Category>
中没有内容,<slot></slot>
标签中的内容就会显示在页面。
这里父组件被称为插槽的使用者
实现:
Category.vue:
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 插槽就相当于定义一个坑,等组件来填充 -->
<slot>我是一个插槽,如果组件标签中没有传递值时,我就显示,否则显示组件传递过来的内容</slot>
</div>
</template>
<script>
export default {
name: 'CategoryVue',
props:['listData','title']
}
</script>
<style>
.category{
width:200px;
height: 300px;
background: lightseagreen;
overflow: hidden;
}
h3{
text-align: center;
background: lightgoldenrodyellow;
}
</style>
App.vue
<template>
<div class="container">
<Category title="美食">
<img src="./assets/1.jpg" />
</Category>
<Category title="游戏">
<ul>
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from "@/components/Category";
export default {
name: "App",
data() {
return {
foods: ['烧烤', '火锅', '小龙虾', '螺蛳粉'],
films: ['《当幸福来敲门》', '《泰坦尼克号》', '《阿甘正传》', '《海上钢琴师》'],
games:['王者荣耀','我的世界','和平精英','穿越火线']
}
},
components: {
Category
}
}
</script>
<style lang="css">
.container{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
实现效果:
如果App.vue的子组件标签中不放内容,就会将是如下样式:(将slot的内容进行显示)
插槽的编译
app.vue子组件标签中的html代码片段是编译完之后再放到子组件的插槽中去,所以该html代码片段的样式既可以卸载app.vue
文件中,也可以写在子组件
中。
具名插槽
具名插槽的使用
一个子组件可能有多个插槽,不同的插槽放置不同的内容,为了区分不同的插槽,每个插槽应该有自己的名字
,这就是具名插槽
。
同时app.vue子组件标签中的html代码片段的内容也需要设置slot属性
,用于指明该片段放在哪个插槽中。
如果两个片段放在一个插槽中,不会覆盖只是追加。
category.vue(子组件)
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 插槽就相当于定义一个坑,等组件来填充 -->
<slot name="center">我是一个插槽,如果组件标签中没有传递值时,我就显示,否则显示组件传递过来的内容center</slot>
<slot name="footer">我是一个插槽,如果组件标签中没有传递值时,我就显示,否则显示组件传递过来的内容footer</slot>
</div>
</template>
<script>
export default {
name: 'CategoryVue',
props:['listData','title']
}
</script>
<style>
.category{
width:200px;
height: 300px;
background: lightseagreen;
overflow: hidden;
}
h3{
text-align: center;
background: lightgoldenrodyellow;
}
</style>
app.vue(父组件)
<template>
<div class="container">
<Category title="美食">
<img slot="center" href="./assets/1.jpg" />
<a slot="footer" href="https://www.baidu.com">点我查看更多</a>
</Category>
<Category title="游戏">
<ul slot="center">
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
<a slot="footer" href="https://www.baidu.com">单机游戏</a>
<a slot="footer" href="https://www.baidu.com">网络游戏</a>
</Category>
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<div class="foot" slot="footer">
<a href="https://www.baidu.com">经典</a>
<a href="https://www.baidu.com">热门</a>
<a href="https://www.baidu.com">推荐</a>
</div>
</Category>
</div>
</template>
<script>
import Category from "@/components/Category";
export default {
name: "App",
data() {
return {
foods: ['烧烤', '火锅', '小龙虾', '螺蛳粉'],
films: ['《当幸福来敲门》', '《泰坦尼克号》', '《阿甘正传》', '《海上钢琴师》'],
games:['王者荣耀','我的世界','和平精英','穿越火线']
}
},
components: {
Category
}
}
</script>
<style lang="css">
.container,.foot{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
height: 50%;
}
video{
width: 100%;
}
</style>
效果:
template
当slot属性
碰上template标签
可以这样写:
<template v-slot:footer></template>
同时v-slot:
可以简写成#
即<template #footer></template>
eg:
<template v-slot:footer>
<div class="foot">
<a href="https://www.baidu.com">经典</a>
<a href="https://www.baidu.com">热门</a>
<a href="https://www.baidu.com">推荐</a>
</div>
<h4>欢迎观看电影</h4>
</template>
具名插槽的默认名称
没有提供 name
的 <slot>
出口会隐式地命名为“default”
。
eg:
子组件:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
父组件传递:
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
父组件的写法等同于:
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<!-- 隐式的默认插槽 -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
动态插槽名
动态指令参数
在 v-slot
上也是有效的,即可以定义下面这样的动态插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
作用域插槽的应用场景:
数据
在子组件
中,而根据数据生成的结构
在插槽的使用者
上。
(插槽的使用者即使用子组件的父组件)。
传递数据
因为插槽的使用者
(父组件)需要使用子组件的数据, 所以需要将数据从子组件传递到插槽的使用者
中。
- 子组件的传递方法:利用插槽标签
<slot></slot>
结合v-bind指令进行传递:
<slot :定义属性名="传递的data数据"></slot>
可以传递多个数据 - 父组件接收数据的方法有两种:默认作用域
默认作用域插槽接受方法
插槽的使用者
接收的方法:通过子组件标签上
的v-slot
指令直接接受一个插槽的props
对象。
子组件传入插槽的props
作为v-slot
指令的值可以在插槽内的表达式中访问。
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
具名作用域插槽接受方法
具名作用域插槽的工作方式和普通作用域插槽的工作方式类似,插槽的props
可以作为v-slot
指令的值v-slot:name="slotProps"
被访问到,也可以使用简写#name="slotProps"
访问。
子组件传递props属性:
<slot name="header" message="hello"></slot>
父组件接受props属性:
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
父组件headerProps接受到的数据是 { message: 'hello' }
子组件slot
标签的name
属性是不会传递給插槽的。
默认作用域插槽和具名作用域插槽一起使用
当一起使用的时候相当于为默认插槽设置了一个default
的名字,使用<template>
标签和v-slot:default
接受默认作用域插槽的属性值。具名作用域插槽正常使用即可。
<template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
vue2作用域插槽接受方法
插槽的使用者
接收的方法:使用<template></template>
标签的scope
属性接收从子组件通过<slot>
标签传递过来的所有数据,接收到的数据是一个对象。
也可以使用<template></template>
标签的slot-scope
属性接收数据。
scope和slot-scope接收数据支持解构赋值
。
<template scope="自己起的变量名">
</template>
使用:
- 子组件category.vue —— 数据在子组件中
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 插槽就相当于定义一个坑,等组件来填充 -->
<!--slot将games传递给插槽的使用者 -->
<slot :games="games">我是一个插槽</slot>
</div>
</template>
<script>
export default {
name: 'CategoryVue',
props: ['title'],
data() {
return{
games: ['王者荣耀', '我的世界', '和平精英', '穿越火线']
}
}
}
</script>
<style>
.category{
width:200px;
height: 300px;
background: lightseagreen;
overflow: hidden;
}
h3{
text-align: center;
background: lightgoldenrodyellow;
}
</style>
- 父组件App.vue(
插槽的使用者
) —— 结构在父组件中
<template>
<div class="container">
<Category title="游戏">
<!-- template利用scope属性接受插槽传过来的数据 -->
<!-- scope接收的数据是一个对象 -->
<template scope="youxi">
<ul>
<li v-for="(game,index) in youxi.games" :key="index">{{game}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<!-- 支持解构赋值 -->
<template scope="{games}">
<ol>
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<!-- 也可以使用slot-scope属性接受值 -->
<template slot-scope="{games}">
<h4 v-for="(game,index) in games" :key="index">{{game}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "@/components/Category";
export default {
name: "App",
components: {
Category
}
}
</script>
<style lang="css">
.container{
display: flex;
justify-content: space-around;
}
</style>
实现的效果:
好几使用示例
子组件可以循环使用<slot>
:
FancyList.vue:
<ul>
<li v-for="item in items">
<slot name="item" v-bind="item"></slot>
</li>
</ul>
父组件使用:
<FancyList :api-url="url" :per-page="10">
<template #item="{ body, username, likes }">
<div class="item">
<p>{{ body }}</p>
<p>by {{ username }} | {{ likes }} likes</p>
</div>
</template>
</FancyList>
无渲染组件
简单来说就是:只有逻辑内容没有渲染内容的子组件叫做无渲染组件,子组件的渲染通过具名插槽全权交给父组件渲染。
上面的 <FancyList>
案例同时封装了可重用的逻辑 (数据获取、分页等) 和视图输出,但也将部分视图输出通过作用域插槽交给了消费者组件
来管理。
如果我们将这个概念拓展一下,可以想象的是,一些组件可能只包括了逻辑而不需要自己渲染内容
,视图输出通过作用域插槽全权交给了消费者组件
。我们将这种类型的组件称为无渲染组件
。
插槽总结
- 插槽的作用:
让父组件可以向子组件指定位置插入html结构
,也是一种组件间通信的方式,适用于父组件===>子组件
。
使用插槽的时候父组件向子组件传递的html内容,在组件实例对象的$slots属性
上存储着父组件向子组件传递的html内容