一、插槽
1.了解插槽
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
<div id='app'>
<my-cmp>
Something bad happened.
</my-cmp>
</div>
如果有这样的需求,我们就可以通过插槽来做。
2.插槽内容
通过插槽,我们可以这样合成组件:
<div id="app">
<my-cmp>这是一个插槽</my-cmp>
<my-cmp><h3>这是一个插槽</h3></my-cmp>
</div>
<script>
Vue.component('my-cmp',{
template: `
<div>
<slot></slot>
</div>
`
});
</script>
当组件渲染时,<slot></slot>
将会被替换为“写在组件标签结构中的内容”。
插槽内可以包含任何模板代码,包括HTML和其他组件。
如果<my-cmp>
没有包含<slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
3.编译作用域
当在插槽中使用数据时:
<div id="app">
<my-cmp>这是一个插槽{
{ name }}</my-cmp>
</div>
<script>
Vue.component('my-cmp',{
template: `
<div>
<slot></slot>
</div>
`
});
const vm = new Vue({
el: '#app',
data: {
name: 'jimo'
}
});
</script>
该插槽跟模板的其他地方一样可以访问相同的实例属性,也就是相同的“作用域”,而不能访问<my-cmp>
的作用域。
请记住:
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
4.后备内容
我们可以设置默认插槽,它会在没有提供内容时被渲染,如,在<my-cmp>
组件中:
我们希望这个<div>
内绝大多数情况下都渲染文本 “这是插槽默认内容”,此时就可以将 “这是插槽默认内容” 作为后备内容,如:
<div id="app">
<my-cmp></my-cmp>
</div>
<script>
Vue.component('my-cmp',{
template: `
<div>
<slot>这是插槽默认内容</slot>
</div>
`
});
</script>
当使用组件未提供插槽时,后备内容将会被渲染。如果提供插槽,则后备内容将会被取代。
5.具名插槽
有时我们需要多个插槽,如<my-cmp>
组件:
Vue.component('my-cmp', {
template: `
<div class="container">
<header>
<!-- 页头 -->
</header>
<main>
<!-- 主要内容 -->
</main>
<footer>
<!-- 页脚 -->
</footer>
</div>
`
});
此时,可以在<slot>
元素上使用一个特殊的特性:name。利用这个特性定义额外的插槽:
Vue.component('my-cmp', {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
});
一个不带 name 的 <slot>
出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
<div id="app">
<my-cmp>
<template v-slot:header>
<h1>头部</h1>
</template>
<p>内容</p>
<p>内容</p>
<template v-slot:footer>
<p>底部</p>
</template>
</my-cmp>
</div>
<script>
Vue.component('my-cmp', {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
});
</script>
现在<template>
元素中的所有内容都会被传入相应的插槽。任何没有被包裹在带有v-slot
的<template>
中的内容都会被视为默认插槽的内容。
为了模板更清晰,也可以写成以下这样:
<div id="app">
<my-cmp>
<template v-slot:header>
<h1>头部</h1>
</template>
<template v-slot:default>
<p>内容</p>
<p>内容</p>
</template>
<template v-slot:footer>
<p>底部</p>
</template>
</my-cmp>
</div>
注意:v-slot只能添加在<template>
上,只有一种例外情况。
6.作用域插槽
为了能够让插槽内容访问子组件的数据,我们可以将子组件的数据作为<slot>
元素的一个特性绑定上去:
Vue.component('my-cmp', {
data () {
return {
user: {
name: '悸沫',
age: 18,
}
}
},
template: `
<span>
<slot v-bind:user="user"></slot>
</span>
`
});
绑定在 <slot>
元素上的特性被称为插槽 prop。
那么在父级作用域中,我们可以给v-slot
带一个值来定义我们提供的插槽prop的名字:
<div id="app">
<my-cmp>
<template v-slot:default="jimo">
{
{ jimo.user.name }}
</template>
</my-cmp>
</div>
1.独占默认插槽的缩写语法
当被提供的内容只有默认插槽时,组件的标签可以被当作插槽的模板来使用,此时,可以将v-slot
直接用在组件上:
<div id="app">
<my-cmp v-slot:default="jimo">
{
{ jimo.user.name }}
</my-cmp>
</div>
或者更简单:
<div id="app">
<my-cmp v-slot="jimo">
{
{ jimo.user.name }}
</my-cmp>
</div>
注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。
<!-- 无效,会导致警告 -->
<my-cmp v-slot="jimo">
{
{ jimo.user.name }}
<template v-slot:other="otherJimo">
jimo在这里是不合法的
</template>
</my-cmp>
所以:只要出现多个插槽,就需要为所有的插槽使用完整的基于<template>
的语法。
2.解构插槽Prop
我们可以使用解构传入具体的插槽prop,如:
<div id="app">
<my-cmp v-slot="{ user }">
{
{ user.name }}
</my-cmp>
</div>
<script>
Vue.component('my-cmp', {
data () {
return {
user: {
name: '悸沫',
age: 18,
}
}
},
template: `
<span>
<slot v-bind:user="user"></slot>
</span>
`
});
</script>
这样模板会更简洁,尤其是在为插槽提供了多个prop时。
此外还可以有其他可能,如prop重命名:
<my-cmp v-slot="{ user: person }">
{
{ person.name }}
</my-cmp>
以及自定义后备内容,当插槽prop是undefined时生效:
<my-cmp v-slot="{ user = { name: 'Guest' } }">
{
{ user.name }}
</my-cmp>
7.动态插槽名
即插槽名可以使用变量
Vue 2.6.0新增
<my-cmp>
<template v-slot:[dynamicSlotName]
...
</template>
</my-cmp>
8.具名插槽的缩写
Vue 2.6.0新增
跟v-on
和v-bind
一样,v-slot
也有缩写,将v-slot:
替换为#
。
<my-cmp>
<template #header>
<h1>头部</h1>
</template>
<template #default>
<p>内容</p>
<p>内容</p>
</template>
<template #footer>
<p>底部</p>
</template>
</my-cmp>
当然,和其它指令一样,该缩写只在其有参数的时候才可用。