介绍
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 。Vue.js 实现了一个内容分发 API,使用特殊的 ‘slot’ 元素作为原始内容的插槽。
编译作用域
在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译。假定模板为:
<child-component>
{{ msg }}
</child-component>
msg 应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是:
*父组件模板的内容在父组件作用域内编译;
*子组件模板的内容在子组件作用域内编译。
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
<!-- 无效 -->
<child-component v-show="someChildProperty"></child-component>
假定 someChildProperty 是子组件的属性,上例不会如预期那样工作。父组件模板不应该知道子组件的状态。
如果要绑定作用域内的指令到一个组件的根节点,你应当在组件自己的模板上做
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})
类似地,分发内容是在父作用域内编译。
单个slot
除非子组件模板包含至少一个 ‘slot’ 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。
最初在 ‘slot’ 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
示例代码:
<div id="app">
<h2>我是父组件的标题</h2>
<my-component>
<p>这是父组件分发的内容1</p>
<p>这是父组件分发的内容2</p>
</my-component>
</div>
Vue.component('my-component',{
template:`
<div>
<h4>我是子组件的标题</h4>
<slot>
只有在没有要分发的内容是才出现。
</slot>
<div>
`,
})
new Vue({
el:'#app'
})
运行结果如下:
将html部分代码修改为以下代码:
<div id="app">
<h1>我是父组件的标题</h1>
<my-component>
</my-component>
</div>
运行结果如下:
具名slot
‘slot’
元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
如以下例子:
<div id="app">
<my-component>
<h1 slot="header">这是标题</h1>
<p>第一个段落</p>
<p>第二个段落</p>
<p>第三个段落</p>
<p slot="footer">联系信息</p>
</my-component>
</div>
Vue.component('my-component',{
template:`
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`,
})
new Vue({
el:'#app'
})
作用域插槽
作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。
在子组件中,只需将数据传递到插槽,就像你将 props 传递给组件一样。
示例代码:
<div id="app">
<my-component>
<template scope="props">
<p>hello from parent</p>
<p>{{props.text}}</p>
</template>
</my-component>
</div>
Vue.component('my-component',{
template:`
<div class="child">
<slot text="hello from child"></slot>
<div>
`,
props:['text']
})
new Vue({
el:'#app'
})
在父级中,具有特殊属性 scope 的 <’template’> 元素必须存在,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象。
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项:
<div id="app">
<my-component :items="items">
<template slot="item" scope="props">
<li>{{props.text}}</li>
</template>
</my-component>
</div>
Vue.component('my-component',{
template:`
<ul>
<slot name="item" v-for="item in items" :text="item.text"></slot>
</ul>
`,
props:['text','items']
})
new Vue({
el:'#app',
data:{
items:[
{text:'item1'},
{text:'item2'},
{text:'item3'},
]
}
})
作用域插槽也可以是具名的
以下内容转载自官方文档 传送门
有的时候你希望提供的组件带有一个可从子组件获取数据的可复用的插槽。例如一个简单的 <todo-list>
组件的模板可能包含了如下代码:
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
但是在我们应用的某些部分,我们希望每个独立的待办项渲染出和 todo.text 不太一样的东西。这也是作用域插槽的用武之地。
为了让这个特性成为可能,你需要做的全部事情就是将待办项内容包裹在一个
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
现在当我们使用 在 2.5.0+,slot-scope 不再限制在 元素上使用,而可以用在插槽内的任何元素或组件上。
如果一个 JavaScript 表达式在一个函数定义的参数位置有效,那么这个表达式实际上就可以被 slot-scope 接受。也就是说你可以在支持的环境下 (单文件组件或现代浏览器),在这些表达式中使用 ES2015 解构语法。例如:<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>
解构 slot-scope
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>