重点:
单个插槽、具名插槽、作用域插槽的用法;
访问插槽的方法。
其实本小白对插槽理解的还不深,哪些场景会经常用到插槽也不了解。
但是本着“大胆猜测”的理念,我的猜测如下:
- 假设有 父组件A,有 子组件B、子组件C、子组件D;
- 三个子组件要显示不同的内容,于是可以在三个子组件中各自写不同的内容;
- 但是如果子组件中有重复的部分,那就会产生冗余的代码,而且修改起来也不方便;
- 于是可是将这些重复的部分,放到他们的 父组件A 中,由 父组件 统一管理;
- 哪个子组件要用到这些内容, 父组件就把内容发给谁,也就是“内容分发”;
- 由父组件统一管理,还有一个好处就是,只需要修改父组件中的内容,子组件就会随之改变;
- “内容分发”的过程,又仿佛与组件通信的过程类似;
- 比如:父组件要怎么“发内容”呢?子组件要怎么“收内容”呢?
- 于是“插槽(slot)”就登场了;
- 只需要在子组件中使用 slot 元素,就可以将内容显示出来。
接下来,就主要介绍“单个插槽”、“具名插槽”、“作用域插槽”的用法。
一、单个插槽
首先请看:
<div id="app"> <child-component> <p>来自父组件的问候</p> // 这看似是在子组件中写的内容,但其实这是属于父组件的作用域</child-component> // 也就是说,其实这个 p元素 ,是父组件中的内容
</div> Vue.component('child-component', {template: `<div></div>`
})var app = new Vue({el: '#app',data: {}
})
如果像上面这样,直接在 <child-component></child-component>中写东西,是不会被渲染的,因为子组件还没接收你写的东西。子组件里只有一个空的 div 元素,所以页面只渲染一个空的 div ,如图:
那么子组件要如何接收父组件传来的内容呢?答案是,在子组件中使用 slot 元素就好了:
Vue.component('child-component', {template: `<div><slot></slot> // 在子组件中使用 slot 元素</div>`
})
看,现在页面就渲染出了父组件中写的 p 元素:
总结:
在子组件中使用 slot 做一个插槽,父组件中的内容就可以插到这个插槽里边。
二、具名插槽
顾名思义,具名插槽,就是有名字的插槽。
如果父组件中有很多内容都要放到子组件中,如果子组件中只有一个 slot(插槽)的话,那所有内容都挤到一起,显然是不合理的。所以我们需要多个 slot(插槽),并给每个 slot(插槽)起个名字,那么就能很方便的讲指定内容放到指定 slot(插槽)中。
比如父组件中有一首诗:
<div id="app"><child-component><p>离离原上草,</p> // 注意,这里的作用域还是父组件的作用域<p>一岁一枯荣。</p><p>野火烧不尽,</p><p>春风吹又生。</p><address>白居易</address><h2>赋得古原草送别</h2></child-component>
</div>Vue.component('child-component', {template: `<div><slot></slot> // 子组件中只有一个 slot(插槽),那么所有内容都将按顺序被插到这个 slot 中</div>`
})var app = new Vue({el: '#app',data: {}
})
渲染:
显然这样是不合适的,如果有更多的内容,那么更多的内容都将被插到这一个 slot 中。
于是我们在子组件中多写几个 slot,并给他们名字:
Vue.component('child-component', {template: `<div><slot name="header"></slot> // 有 name<slot></slot> // 没有 name ?这个先放着,后面再说<slot name="footer"></slot> // 有 name</div>`
})
那么相应的,也要给父组件中的内容起个名字,不然怎么知道哪个插到哪里呢??但是要注意的是,给父组件中的内容起名字,要用到 slot,而不是 name:
<div id="app"><child-component><p>离离原上草,</p><p>一岁一枯荣。</p><p>野火烧不尽,</p><p>春风吹又生。</p> // 上面的四个 p 元素没名字?<address slot="footer">白居易</address> // 用 slot 起名字<h2 slot="header">赋得古原草送别</h2> // 用 slot 起名字</child-component>
</div>
先看看页面是怎么渲染的:
这里需要注意几点:
- 页面渲染的顺序,是子组件中 slot 的顺序;
- 子组件中有名字的 slot,显示父组件中有对应名字的内容;没有名字的 slot,显示父组件中没有名字的内容;
三、作用域插槽
上面说的单个插槽和具名插槽,都是父组件向子组件传递数据。那么,父组件要从子组件中获取数据,怎么办呢?同样,使用 slot 就可以办到。
<div id="app"><child-component><template slot="text1" slot-scope="data"> // 这里用的 template{{ data.text }}</template><p slot="text2" slot-scope="data"> // 这里用的 p{{ data.text }}</p></child-component>
</div>Vue.component('child-component', {template: `<div><slot name="text1" text="我多想再见你,哪怕匆匆一眼就别离"></slot><slot name="text2" text="路灯下昏黄的剪影,越走越漫长的林径"></slot></div>`
})var app = new Vue({el: '#app',data: {}
})
以上代码中,子组件中使用了两个 slot 并且都有 name 属性,对应了父组件中的 template 元素 和 p 元素。
为什么要用两个不同的元素呢?原因是,在 Vue 2.5.0 版本之前,要想使用作用域插槽获取子组件数据,必须使用 template 元素;而在 Vue 2.5.0 版本之后,就可以使用其他元素了。所以上面代码中,使用了 p 元素做了一个测试,你也可以尝试其他元素。
接着看上面代码,子组件中的 text,就是子组件中的数据。看看父组件是怎么获取的?它分别在 template 和 p 上写了 slot-scope 属性,scope 有“范围”的意思,也就是 slot 的范围,也就是“作用域插槽”了。同时我们还看到:
slot-scope="data"
data 其实就是一个变量名,你也可以换成其他的名字,比如 prop,如果换成 prop,你就要这么写:
<template slot="text1" slot-scope="prop"> {{ prop.text }}
</template>
四、访问插槽
还记得上篇文章的子链吗?父链 和 子链
如果有多个子链,我们访问子链的时候,就要用到 this.$refs。
同样的,如果有多个插槽,我们访问插槽的时候,就要用到 this.$slots。
<div id="app"><child-component><p>离离原上草</p><p>一岁一枯荣</p><p>野火烧不尽</p><p>春风吹又生</p><address slot="footer">白居易</address><h2 slot="header">赋得古原草送别</h2></child-component>
</div>Vue.component('child-component', {template: `<div><slot name="header"></slot><slot name="footer"></slot><slot></slot></div>`,mounted: function () {let header = this.$slots.header // 访问名字为 header 的插槽let footer = this.$slots.footer // 访问名字为 footer 的插槽let headerText = header[0].elm.innerText // header 中的元素的文本let footerText = footer[0].elm.innerText // footer 中的元素的文本console.log(headerText)console.log(footerText)}
})var app = new Vue({el: '#app',data: {}
})
控制台打印:
如果你觉得插槽不好理解的话,你就想象小时候玩的“小霸王”。是不是得插个卡到那个卡槽里,才能开始玩游戏?那个卡槽,就相当于子组件中的 slot,游戏卡就相当于父组件中的内容。你得把内容插到 slot 里,子组件才能正确被渲染。