插槽
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法(即
v-slot
指令),它取代了slot
和slot-scope
这两个目前已经被废弃但仍为被移除且仍在文档中的attribute
。
插槽内容
Vue 实现了一套内容分发的 API,将 <slot>
元素作为承载分发内容的出口。
在组件内使用插槽
navigation.vue
<template>
<div>
<a :href="url" class="nav-link">
<slot></slot>
</a>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
url: String
},
}
</script>
<style>
</style>
调用上面的组件
slot.vue
<template>
<div>
<navigation url="/profile">Your Profile</navigation>
</div>
</template>
<script>
import navigation from './navigation'
export default {
name: 'slotEle',
data() {
return {}
},
components: {
navigation,
},
}
</script>
<style>
</style>
渲染结果:
如果 navigation
的 template
中没有包含一个 <slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
编译作用域
父级模板里的所有内容都是在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的
后备内容
有时为一个插槽设置具体的后备(也就是默认的)内容是很有用的,它只会在没有提供内容的时候被渲染
如在navigation
组件中:
<template>
<div>
<a :href="url" class="nav-link">
<slot>我是链接</slot>
</a>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
url: String
},
methods: {}
}
</script>
- 如果父级组件中使用子组件,不提供任何插槽内容,想要让超链接显示
我是链接
的文本,则需要将后备内容放在<slot>
标签内,此时后备内容将会被渲染
父级组件中使用<navigation>
组件
<navigation url="/profile"></navigation>
- 如果父组件中提供了内容
<navigation url="/profile">Your profile</navigation>
则这个提供的内容将会被渲染从而取代后备内容
具名插槽
有时我们需要多个插槽,例如一个带有如下模板的 <base-layout>
组件
baseLayout.vue
<template>
<div class="container">
<header>
<!-- <slot> 元素有一个特殊的 attribute: name ,这个 attribute 可以
用来定义额外的插槽 -->
<slot name="header"></slot>
</header>
<main>
<!-- 一个不带 name 的 <slot> 出口会带有隐含的名字 default -->
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
在向具名插槽提供内容的时候,我们可以在一 个 template
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称
父组件 slot.vue
<!-- 方式一 -->
<base-layout>
<template v-slot:header>我是header内容</template>
<!-- 没有包裹在带有 v-slot 的 <template> 中的内容
都会被视为默认插槽的内容 -->
<template>我是main内容</template>
<template v-slot:footer>我是footer内容</template>
</base-layout>
如果你希望更明确一些,可以在一个 <template>
中包裹默认插槽的内容
<!-- 方式二 -->
<base-layout>
<template v-slot:header>我是header内容</template>
<!-- 没有包裹在带有 v-slot 的 <template> 中的内容
都会被视为默认插槽的内容 -->
<template v-slot:default>我是main内容</template>
<template v-slot:footer>我是footer内容</template>
</base-layout>
任何一种写法都会渲染出
注意:v-slot
只能添加在 <template>
上
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。如一个带有如下模板的 current-user
组件
currentUser.vue
<template>
<div>
<span>
<slot :user="user">{{user.lastName}}</slot>
</span>
</div>
</template>
<script>
export default {
data() {
return {
user: {
firstName: 'du',
lastName: 'li'
}
}
}
}
</script>
slot.vue
<current-user>
<!-- 为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 slot 元素的一个 attribute 绑定上去 -->
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
绑定在 <slot>
元素上的 attribute
被称为 插槽prop
,现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽prop的名字。
我们选择将包含所有插槽 prop 的对象命名为 slotProps
,但是你也可以自己定义任何名字
独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板来使用。这样我们就可以把 v-slot
直接用在组件上
<current-user v-slot:default="slotPorps>
{{ slotProps.user.firstName }}
</current-user>
这种写法还可以更简单,就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot
被假定对应默认插槽
<current-user v-slot="slotPorps>
{{ slotProps.user.firstName }}
</current-user>
**注意:**默认插槽的缩写语法不能喝具名插槽混用,因为它会导致作用域不明确。
只要出现多个插槽,请始终未所有的插槽实验完整的基于 <template>
的语法。
解构插槽prop
作用域插槽的内部工作原理是讲你的插槽内容包括在一个传入单个参数的函数里
function (slotProps) {
// 插槽内容
}
这意味着 v-slot
的值实际上可以是任何能够作为函数定义中的参数的 javascript 表达式。所以在支持的环境下(单文件组件或现代浏览器),你也可以使用ES2015解构来传入具体的插槽 prop。
<current-user>
<!-- 解构插槽 prop -->
<template v-slot="{ user }">
{{ user.firstName }}
</template>
</current-user>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop的时候。它同样开启了 prop 重命名等其他可能,例如。将 user
重命名为 person
<current-user>
<template v-slot="{ user: person }">
{{ person.firstName }}
</template>
</current-user>
甚至可以后备内容,用于插槽 prop 是 undefined 的情形
<current-user>
<template v-slot="{ user = { firstName: 'duli' } }">
{{ user.firstName }}
</template>
</current-user>
动态插槽名
动态指令参数 也可以用在 v-slot
上,来定义动态的插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的缩写
v-slot
也有缩写,即把参数之前的所有内容(v-slot:
) 替换为字符 #
。如 v-slot:header
缩写为 #header
<!-- 方式三 缩写 -->
<base-layout>
<template #header>我是header内容</template>
<template >我是main内容</template>
<template #footer>我是footer内容</template>
</base-layout>
和其他指令一样,该缩写只有在其有参数时才可用。这意味着以下语法是无效的
<current-user>
// 这样会报错
<template #="{ user }">
{{ user.firstName }}
</template>
</current-user>
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之
<current-user>
<template #default="{ user }">
{{ user.firstName }}
</template>
</current-user>
其它示例
**插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。**这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件时是最有用的。
例如,我们要实现一个 <todo-list>
组件,它是一个列表且包含布局和过滤逻辑
我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo 作为一个插槽 prop 进行绑定。
todoList.vue
<template>
<div>
<ul>
<li v-for="todo in todos" :key="todo.id">
<!-- 我们为每个 todo 准备了一个插槽,将 todo 对象作为一个插槽的 prop 传入 -->
<slot name="todo" :todo="todo">
<!-- 后备内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
}
},
props: {
todos: Array
}
}
</script>
当我们使用 <todo-list>
组件的时候,我们可以选择为 todo 定义一个不一样的 <template>
作为替代方案,并且可以从子组件获取数据
slot.vue
<template>
<div>
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">x</span>
{{ todo.text }}
</template>
</todo-list>
</div>
</template>
<script>
export default {
data() {
return {
todos: [
{id: 1,text: '香蕉', isComplete: true,},
{id: 2,text: '苹果',isComplete: true,},
{id: 3,text: '梨子',isComplete: false,},
]
}
},
components: {
todoList,
},
}
</script>