# Vue内容分发slot
为了让组件可以组合,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 (或 “transclusion” )。
Vue实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 <slot>
元素作为原始内容的插槽。
# 编译作用域
当你想在一个插槽中使用数据时,例如:
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
该插槽跟模板的其它地方一样可以访问相同的实例 property (也就是相同的“作用域”),而不能访问 <navigation-link>
的作用域。例如 url 是访问不到的:
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
TIP
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
# 默认丢弃
一般地,如果子组件模板不包含 <slot>
插口,父组件的内容将会被丢弃。
<!-- parent -->
<div class="parent">
<p>父组件</p>
<child>
<p>测试无 slot 插槽</p>
</child>
</div>
<!-- child -->
<div class="child">
<p>子组件</p>
</div>
最终渲染如下
<div class="parent">
<p>父组件</p>
<div class="child">
<p>子组件</p>
</div>
</div>
<!-- <p>测试无 slot 插槽</p> 被丢弃 -->
# 内联模板
- 当
inline-template
这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。
WARNING
不过,inline-template
会让模板的作用域变得更加难以理解。所以作为最佳实践,在组件内优先选择 template
选项或 .vue
文件里的一个 <template>
元素来定义模板。
<div class="child">
<p>子组件</p>
</div>
<div class="parent">
<p>父组件</p>
<child inline-template>
<p>测试inline-template</p>
</child>
</div>
最终渲染为如下内容
<div class="parent">
<p>父组件</p>
<p>测试inline-template</p>
</div>
# 匿名插槽
TIP
当子组件模板只有一个没有属性的 slot
时,父组件整个内容片段将插入到 slot
所在的 DOM
位置,并替换掉 slot
标签本身
匿名slot只能作为没有slot属性的元素的插槽,有slot属性的元素如果没有配置slot,则会被抛弃
<!-- child组件 -->
<div class="child">
<p>子组件</p>
<slot></slot>
</div>
<!-- parent -->
<div class="parent">
<p>父组件</p>
<child>
<p>测试匿名插槽slot</p>
</child>
</div>
渲染结果
<div class="parent">
<p>父组件</p>
<div class="child">
<p>子组件</p>
<p>测试匿名插槽slot</p>
</div>
</div>
DANGER
如果出现多于1个的匿名slot,vue将报错。错误示例如下:
<div class="child">
<p>子组件</p>
<slot></slot>
<slot></slot>
</div>
Duplicate presence of slot “default” found in the same render tree - this will likely cause render errors.
# 插槽默认值
- 最初在
<slot>
标签中的任何内容都被视为备用内容,或者称为默认值。 - 备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容
<!-- child -->
<div class="child">
<p>子组件</p>
<slot>
<p>我是默认值</p>
</slot>
</div>
<div class="parent">
<p>父组件</p>
<child></child>
</div>
渲染结果
<div class="parent">
<p>父组件</p>
<div class="child">
<p>子组件</p>
<p>我是插槽默认值</p>
</div>
</div>
- 当slot存在默认值,且父元素在
<child>
中存在要插入的内容时,则显示设置值。
<!-- parent -->
<div class="parent">
<p>父组件</p>
<child>
<p>我是设置值,会覆盖默认值</p>
</child>
</div>
渲染结果
<div class="parent">
<p>父组件</p>
<div class="child">
<p>子组件</p>
<p>我是设置值,会覆盖默认值</p>
</div>
</div>
# 具名slot
<slot>
元素可以用一个特殊的属性name
来配置如何分发内容。- 多个
slot
可以有不同的名字。 - 具名
slot
将匹配内容片段中有对应slot
特性的元素 - 一个不带 name 的
<slot>
出口会带有隐含的名字“default”
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
<!-- ===============> -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
# 具名slot缩写
TIP
2.6.0 新增
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:)
替换为字符 #。
例如 v-slot:header 可以被重写为 #header
<!-- 和其它指令一样,该缩写只在其有参数的时候才可用。 -->
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
WARNING
如果希望使用缩写的话,你必须始终以明确插槽名取而代之:
# 作用域插槽
- 作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。
- 在子组件中,只需将数据传递到插槽,就像将 props 传递给组件一样
- 【可参考:element ui el-table-column 自定义】
在父级中,具有特殊属性 scope
的 <template>
元素必须存在,表示它是作用域插槽的模板。
scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象
<!-- child -->
<div class="child">
<p>子组件</p>
<slot xxx="msg from child"></slot>
</div>
<!-- parent -->
<div class="parent">
<p>父组件</p>
<child>
<template scope="props">
<p>hello from parent</p>
<p>{{ props.xxx }}</p>
</template>
</child>
</div>
渲染结果
<div class="parent">
<p>父组件</p>
<div class="child">
<p>子组件</p>
<p>msg from parent</p>
<p>msg from child</p>
</div>
</div>
# 列表组件
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项
var childNode = {
template: `
<ul>
<slot name="item" v-for="item in items" :text="item.text">默认值</slot>
</ul>
`,
data(){
return{
items:[
{id:1,text:'第1段'},
{id:2,text:'第2段'},
{id:3,text:'第3段'},
]
}
}
};
var parentNode = {
template: `
<div class="parent">
<p>父组件</p>
<child>
<template slot="item" scope="props">
<li>{{ props.text }}</li>
</template>
</child>
</div>
`,
components: {
'child': childNode
},
};
# 废弃的语法
# 带有slot attribute 的具名插槽
TIP
自 2.6.0 起被废弃。
# 带有slot-scope attribute 的作用域插槽
TIP
自 2.6.0 起被废弃。