1.为什么使用插槽
slot翻译为插槽:
- 在生活中很多地方都用到插槽,电脑的USB插槽,插板有电源插槽。
- 插槽的目的是让我们原来的设备具有更多的扩展性。
- 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
组件的插槽:
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么。
2.插槽的使用
如何使用插槽:
- 在子组件中,使用特殊的元素< slot >就可以为子组件开启一个插槽。
- 该插槽插入什么内容取决于父组件如何使用。
下面先来看个简单例子:
1.创建一个Home.vue组件
<template>
<div>
<h1>我是Home组件</h1>
<slot></slot>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
在template模板中定义了一个插槽< slot >,这样我们在使用这个组件时就可以插入任意标签。
2.在App.vue组件中注册Home组件并使用Home组件
<template>
<div id="app">
<h1>我是App组件</h1>
<Home>
<h2>我是slot的元素</h2>
</Home>
<Home>
<p>我在插槽中可以任意使用标签</p>
</Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: { Home },
};
</script>
<style>
</style>
从上面代码,我两次使用Home组件,而且分别向插槽传入不同的标签,就是为了显示slot的扩展性。
看效果:
h2 和 p 标签都可以替换slot标签,表明了slot的扩展性。即插槽给组件预留了占位,然后想在里面放什么都可以。
3. 具名插槽
当子组件的功能复杂时,子组件的插槽可能并非是一个。
- 比如我们封装一个导航栏的子组件,可能需要三个插槽,分别代表左边、中间、右边。
- 那么,外面在给插槽插入内容时,如何区分插入的是哪一个插槽呢?
- 这个时候,我们就需要给插槽起一个名字。
举例:
1.修改Home组件:
<template>
<div>
<h1>我是Home组件</h1>
<slot name="left"></slot>
<slot></slot>
<slot name="middle"></slot>
<slot name="right"></slot>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
在上面我写了三个具名插槽,一个匿名插槽。
2.在App.vue组件使用Home组件
<template>
<div id="app">
<h1>我是App组件</h1>
<Home>
<h2 slot="left">我替换了具名插槽left</h2>
<span slot="right">我替换了具名插槽right</span>
<p slot="middle">我替换了具名插槽middle</p>
<img src="./assets/logo.png" />
</Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: { Home },
};
</script>
<style>
</style>
在这里我故意弄乱了插槽的使用顺序,就是为了说明具名插槽只会被同名的标签替换,而匿名的插槽也只会被没有slot属性的标签替换。最后显示的顺序是根据子组件定义插槽顺序决定。
4.作用域插槽
作用域插槽是slot一个比较难理解的点,而官方文档表述又不清晰。这里我们用一句话对其做一个总结:父组件替换插槽的标签,但是内容由子组件来提供。即用子组件数据在父组件替换插槽的标签上使用。
举例:
有个需求:
-
子组件中包括一组数据,fruits:[“苹果”,“香蕉”,“榴莲”]
-
需要在多个界面进行展示:
- 某些界面是以水平方向展示的
- 某些界面是以列表形式展示的
- 某些界面直接展示一个数组
-
内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
-
使用slot作用域插槽就可以了
同样是利用上面的App.vue和Home.vue组件:
Home.vue组件定义插槽和数据:
<template>
<div>
<h1>我是Home组件</h1>
<slot>
<ul>
<li v-for="fruit in fruits">{{fruit}}</li>
</ul>
</slot>
</div>
</template>
<script>
export default {
data: function () {
return {
fruits: ["苹果", "香蕉", "榴莲", "石榴"],
};
},
};
</script>
<style>
</style>
如果在父组件不重新定义插槽如何显示数据,则会默认采用Home.vue中的无序列表来展示数据。
App.vue直接使用Home.vue组件:
<template>
<div id="app">
<h1>我是App组件</h1>
<Home></Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: { Home },
};
</script>
<style>
</style>
上面子组件的插槽默认使用了无序列表来展示数据,那么我们如果想在父组件中用其它标签来替换插槽从而实现其它形式来显示数据,这就需要把子组件数据传到父组件。
举一个错误的例子: 在不传递子组件数据的情况下(App.vue),直接在父组件使用该数据用有序列表展示
<template>
<div id="app">
<h1>我是App组件</h1>
<Home>
<ol>
<li v-for="fruit in fruits">{{fruit}}</li>
</ol>
</Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: { Home },
};
</script>
<style>
</style>
上面的fruits并没有在父组件声明。
好了,这也说明了作用域的问题,每个组件的作用域都是封闭的。
那我们如何在父组件上获得子组件定义的数据呢?
在Home.vue组件中传递数据:
<template>
<div>
<h1>我是Home组件</h1>
<slot :a="fruits">
<ul>
<li v-for="fruit in fruits">{{fruit}}</li>
</ul>
</slot>
</div>
</template>
<script>
export default {
data: function () {
return {
fruits: ["苹果", "香蕉", "榴莲", "石榴"],
};
},
};
</script>
<style>
</style>
在< slot >标签中定义动态定义一个属性,属性名任意取,属性值就是你想传到父组件的数据。为了表示这个属性名任意命名,这里使用了变量a,我们要传过去的是fruits。
在App.vue中接收使用:
<template>
<div id="app">
<h1>我是App组件</h1>
<Home>
<div slot-scope="slotObj">
<ol>
<li v-for="fruit in slotObj.a">{{fruit}}</li>
</ol>
</div>
</Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: { Home },
};
</script>
<style>
</style>
注意:这里需要使用一个div包裹slot的替换标签,并且使用slot-scope属性来接收slot对象,这个对象名也是任意取,我这里取slotObj。因为上面我们使用a来传递fruits,所以slotObj.a即是子组件的fruits数组。
好了,这里实现了利用子组件数据,在父组件的插槽替换标签中使用。