当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到 slot,这个过程叫做内容分发(transclusion)。它有两个特点:
- 组件不知道它的挂载点会有什么内容。挂载点的内容是由它的的父组件决定的。
- 组件很可能有它自己的模板。
props传递数据、events触发事件和 slot 内容分发就构成了 Vue 组件的3个 API 来源,再复杂的组件也是由这3部分构成的。
1.单个slot
在子组件内使用特殊的<slot>元素就可以为这个子组件开启一个 slot (插槽),在父组件模板里,插入在子组件内的所有内容将替代子组件 <slot> 标签以及它的内容。示例代码如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
<child-component>
<p>分发的内容</p>
<p>更多分发的内容</p>
</child-component>
</div>
<script src = "../lib/vue.min.js" ></script>
<script>
Vue.component('child-component', {
template: `
<div>
<slot>
<p>如果父组件没有插入内容,我将作为默认出现</p>
</slot>
</div>
`,
data(){
return{
message: '子组件内容'
}
}
})
var app = new Vue({
el: '#app',
})
</script>
</body>
</html>
子组件 child-component 的模板内定义了一个 <slot> 元素,并且用一个 <p>作为默认的内容,在父组件没有使用 slot 时,会渲染这段默认的文本;如果写入了 slot,那就会替换整个 <slot> 。
2.具名 slot
给 <slot> 元素指定一个 name 后可以分发多个内容,具名Slot 可以与单个 Slot 共存。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
<child-component>
<h2 slot='header'>标题</h2>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script src = "../lib/vue.min.js" ></script>
<script>
Vue.component('child-component', {
template: `
<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
`,
data(){
return{
message: '子组件内容'
}
}
})
var app = new Vue({
el: '#app',
})
</script>
</body>
</html>
子组件声明了3个 <slot> 元素,其中在<div class="main">内的<slot>没有使用name特性,它将作为默认 slot 出现,父组件没有使用 slot 特性的元素与内容都将出现在这里。
如果没有指定默认的匿名 slot,父组件内多余的内容片断都将被抛弃。
3.作用域插槽
作用域插槽是一种特殊的 slot ,使用一个可以复用的模板替换已渲染元素。
作用域插槽具代表性的是列表组件,允许组件自定义应该如何渲染列表每一项。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
<child-component>
<template scope="props">
<p>来自父组件的内容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
<br/>
<my-list :books="books">
<template slot="book" scope="props">
<li>{{props.bookName}}</li>
</template>
</my-list>
</div>
<script src = "../lib/vue.min.js" ></script>
<script>
Vue.component('child-component', {
template: `
<div class="container">
<slot msg="来自子组件的内容"></slot>
</div>
`,
data(){
return{
message: '子组件内容'
}
}
})
Vue.component('my-list', {
props:{
books:{
type: Array,
default: function(){
return [];
}
},
},
template: `
<ul>
<slot name="book" v-for="book in books" :book-name="book.name">
<!--这里也可以写默认slot 内容-->
</slot>
</ul>
`,
})
var app = new Vue({
el: '#app',
data:{
books:[
{ name: '《Vue.js 实战》'},
{ name: '《JavaScript 语言精辟》'},
{ name: '《JavaScript 高级程序设计》'}
]
}
})
</script>
</body>
</html>
子组件 my-list 接收一个来自父级的 props 数组 books,并且将它在 name 为 book 的 slot 上使用 v-for 指令循环,同时暴露一个变量 bookName。
4.访问 slot
Vue.js 2.x 提供了用来访问被 slot 分发的内容的方法 $slots。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script src = "../lib/vue.min.js" ></script>
<script>
Vue.component('child-component', {
template: `
<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
`,
mounted(){
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer;
console.log('footer',footer);
console.log(footer[0].elm.innerHTML);
console.log('main',main);
}
})
var app = new Vue({
el: '#app',
})
</script>
</body>
</html>
通过 $slots 可以访问某个具名 slot,this.$slots.default 包括了所有没有被包含在具名 slot 中的节点。