Vue中的插槽
一.引出插槽
当我们有这种需求时,我们肯定会想到封装一个组件。
// 父组件中
<template>
<div id="app">
<Category title="美食" :dataList="foods"></Category>
<Category title="电影" :dataList="films"></Category>
<Category title="歌曲" :dataList="music"></Category>
</div>
</template>
<script>
import Category from "./components/Category.vue"
export default {
name: 'App',
components: {
Category
},
data() {
return {
// classification: ['美食', '游戏', '电影'],
foods: ["油焖大虾", "鱼香肉丝", "糖醋排骨", "辣椒炒肉"],
films: ["盗梦空间", "让子弹飞", "复仇者联盟", "不能说的秘密"],
music: ["以父之名", "七里香", "夜的第七章", "乌克丽丽"]
}
}
}
</script>
// 子组件中
<template>
<div class="wrap">
<h3 class="title">{{ title }}分类</h3>
<ul>
<li v-for="item in dataList" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Category',
props: ['dataList', 'title'],
};
这时,我们用props接收父组件传过来的数据,没有用到插槽就完美完成了需求~
二.插槽介绍
1.默认插槽
但是,需求一般不会这么简单的,每个列表中可能会有个性化的需求,像这样~
当然我们可以通过条件渲染v-for或者v-show实现,但是当这种列表有几百个呢?显然我们不可能写几百个的v-show嘛。
此时,我们肯定不能在组件中加个标签嘛,那样做会导致三个列表中都会有图片。我们想到需要在父组件中定义结构,我们想到了下面的写法。
// 父组件中
<template>
<div id="app">
<Category title="美食">
<div>
<ul>
<li v-for="item in foods" :key="item">{{ item }}</li>
</ul>
<img src="./static/vue.png" alt="">
</div>
</Category>
<Category title="电影">
<ul>
<li v-for="item in films" :key="item">{{ item }}</li>
</ul>
</Category>
<Category title="歌曲">
<div>
<ul>
<li v-for="item in music" :key="item">{{ item }}</li>
</ul>
<img src="./static/qq.png" alt="">
</div>
</Category>
</div>
</template>
我们在组件标签体中加入了标签,但是当我们打开浏览器时发现,标签并没有奏效。这是因为我们写在父组件标签体内的内容是需要插进子组件中,子组件解析完成后才能显示出来的,而Vue并不知道咱们加的标签应该插在组件的哪个部分,所以显示不出来。
Vue给我们提供了一个特殊的标签,用来占据位置。当父组件中有标签体内容时,这个标签被替换为我们写的标签。(挖个坑,等待组件的使用者填充)
// 子组件中
<template>
<div class="wrap">
<h3 class="title">{{title}}分类</h3>
<slot></slot>
</div>
</template>
注意:
标签内允许有内容,它将作为插槽的默认值,使用者不传值给他时,展示默认内容。
2.具名插槽
特殊需求又来了~~~
有些时候,我们在子组件中可能会使用到不止一个标签,用来让父组件不同的内容放进不同的插槽中。
当我们又在子组件中加了一个后,发现并不是我们想的让指定内容加到指定插槽,而是两个插槽中都是一样的内容。
这是因为Vue不知道我们要将哪些内容插到哪些插槽中,我们需要在内容与插槽之间建立一种联系,这时就用到了Vue中的具名插槽。
对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:
// 父组件中
<template>
<div id="app">
<Category title="美食">
<template v-slot:header>
<ul>
<li v-for="item in foods" :key="item">{{ item }}</li>
</ul>
<img src="./static/vue.png" alt="" />
</template>
<template v-slot:footer>
<a href="www.baidu.com">百度</a>
</template>
</Category>
<Category title="电影">
<ul shot="header">
<li v-for="item in films" :key="item">{{ item }}</li>
</ul>
</Category>
<Category title="歌曲">
<template v-slot:header>
<ul>
<li v-for="item in music" :key="item">{{ item }}</li>
</ul>
<img src="./static/vue.png" alt="" />
</template>
<div slot="footer">
<a href="www.jd.com">京东</a>
<a href="www.taobao.com">淘宝</a>
</div>
</Category>
</div>
</template>
// 子组件中
<template>
<div class="wrap">
<h3 class="title">{{title}}分类</h3>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
注意:
这里使用了两种给父组件定义插槽名字的方法。
(1)v-slot:footer
只能在组件或template上使用,这是新的
(2)slot="footer"
可在任意标签上使用,即将废弃
3.作用域插槽
特殊需求又又来了~~
上面的情况是,data数据、html标签结构都存放在父组件中,我们可以轻松的拿到数据并把它渲染在页面上。当data数据存放在子组件中时,这时存在一个作用域问题,父组件无法拿到数据,我们怎样才能拿到数据呢?
这时我们就用到了Vue中的作用域插槽
为了让 films 在父级的插槽内容中可用,我们可以将 films作为 <slot> 元素的一个 attribute 绑定上去;
绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot或者 scope 来定义我们提供的插槽 prop 的名字:
// 父组件中
<template>
<div id="app">
<Category title="电影">
<template scope="comData">
<ul>
<li v-for="item in comData.films" :key="item">{{ item }}</li>
</ul>
</template>
</Category>
<Category title="电影">
<template slot-scope="comData">
<ul>
<li v-for="item in comData.films" :key="item">{{ item }}</li>
</ul>
</template>
</Category>
<Category title="电影">
<template v-slot:default="comData">
<ul>
<li v-for="item in comData.films" :key="item">{{ item }}</li>
</ul>
</template>
</Category>
<Category title="电影">
<template v-slot:default={films}>
<ul>
<li v-for="item in films" :key="item">{{ item }}</li>
</ul>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category.vue";
export default {
name: "App",
components: {
Category,
},
data() {
return {};
},
};
</script>
// 子组件中
<template>
<div class="wrap">
<h3 class="title">{{title}}分类</h3>
<slot :films="films"></slot>
</div>
</template>
<script>
export default {
name: 'Category',
props: ['title'],
data() {
return {
films: ["盗梦空间", "让子弹飞", "复仇者联盟", "不能说的秘密"]
}
}
};
</script>
此时我们拿到了传过来的数据,我们可以先看一下他是什么。
{ "films": [ "盗梦空间", "让子弹飞", "复仇者联盟", "不能说的秘密" ] }
是一个对象,里面包含了films字段,所以我们可以用comData.films拿到电影数据。
同时,我们可以用ES6中解构赋值拿到数据。scope="{films}"
注意:
(1)父组件想要拿到数据,必须用template包裹内容,给template加一个属性scope、slot-scope或者v-slot:default。
scope、slot-scope都为即将废弃的写法。
(2)父组件接受的数据可以起任意名,并不是子组件中叫films,父组件就一定要叫films。
三.拓展
1.默认插槽名字
默认插槽也有名字,叫default
2.v-slot位置问题
之前提过v-slot可以在组件或template上使用,什么情况可以在组件使用呢?
独占默认插槽
<Category title="电影" v-slot:default="{films}">
<ul>
<li v-for="item in films" :key="item">{{ item }}</li>
</ul>
</Category>
3.具名插槽缩写
v-slot:header =======> #header
它必须有参数,即#后必须给他个名字
4.动态插槽名
<Category title="电影">
<template v-slot:[activeName]>
</template>
</Category>