插槽就是子组件中提供给父组件使用的一个占位符,用<slot></slot>
表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>
标签。
插槽的目的在于使组件更具有扩展性,如何封装一个好的组件?就是将共性抽取到组件中,将不同暴露为插槽。一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
一、插槽基本使用
// 父组件 test.vue 内容
<test-child>
{{data.user}} // 替换 <slot></slot> 里面的内容
</test-child>
// 子组件 testChild.vue 内容
<template>
<div>
<slot></slot> // 此插槽 name 默认为 default
// <slot> 也可以给一个后备内容 </slot>
</div>
</template>
注意: 如果 <test-child>
的 template 中没有包含一个 <slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
二、具名插槽
v-slot 自 2.6.0 起有所更新。已废弃的使用 slot attribute 和 slot-scope attribute 的语法看下文第四节。
(1)基本使用
// 父组件 test.vue 内容
<test-child>
<template v-slot:header> // v-slot:header 可以简写为: #header
我是header插槽
</template>
<div>一个不带 name 的 <slot> 出口会带有隐含的名字“default”。</div>
<p>我也属于 <slot></slot> 默认插槽内容</p>
<template v-slot:footer>
我是footer插槽
</template>
</test-child>
// 子组件 testChild.vue 内容
<template>
<header>
<slot name="header"></slot>
</header>
<slot></slot>
<footer>
<slot name="footer"></slot>
</footer>
</template>
注意:v-slot
只能添加在 <template>
上(只有一种特例:看下文独占默认插槽)
(2)独占默认插槽的缩写语法
v-slot的特例就是当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<test-child v-slot>
此子组件中只有默认插槽<slot><slot>是才能在此组件上用v-slot
</test-child>
三、作用域插槽
作用域插槽其根本目的:就是让插槽内容在父组件内能够访问子组件中才有的数据
(1)默认插槽的缩写语法
// 子组件 testChild.vue 内容
<template>
<div>
<slot v-bind:user="user"> //必须绑定 插槽 prop,否则父级访问不到
{{ user.name }}
</slot>
</div>
</template>
// 在父组件 test.vue 内使用
// <test-child v-slot:default="scope"> 下面是简写
<test-child v-slot="scope"> // scope 这个命名可以任意取
{{ scope.user.name }}
</test-child>
绑定在 <slot>
元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
补充: 在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 scope,但你也可以使用任意你喜欢的名字。
(2)默认插槽 和 具名插槽 混用 注意事项
再次申明 v-slot
只能添加在 <template>
上 ,除非是默认插槽的缩写语法
错误示例:
// 这种写法是无效的,会导致警告
<test-child v-slot="slotProps">
{{ slotProps.user.name }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</test-child>
正确写法: 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template>
的语法:
<test-child>
<template v-slot:default="slotProps">
{{ slotProps.user.name }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</test-child>
注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:
(3) 解构插槽 Prop
作用域插槽的内部工作原理:是将你的插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
这意味着 v-slot
的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
<test-child v-slot="{ user }">
{{ user.name }}
</test-child>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user
重命名为 person
:
<test-child v-slot="{ user: person }">
{{ person.name }}
</test-child>
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
// 子组件 test-child 的内容
<template>
<slot></slot>
</template>
// 父组件写法 (子组件没有绑定任何attribute 的时候可自定义后备数据)
<test-child v-slot="{ user = { name: 'Guest' } }">
{{ user.name }}
</test-child>
四、动态插槽名
动态指令参数也可以用在 v-slot 上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
五、废弃的具名插槽
v-slot
指令自Vue 2.6.0
起被引入,提供更好的支持slot
和slot-scope attribute
的 API替代方案。在接下来所有的 2.x 版本中slot
和slot-scope attribute
仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。
(1)带有 slot attribute
的具名插槽
在 <template>
上使用特殊的 slot attribute
,可以将内容从父级传给具名插槽:
<base-layout>
<template 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 slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>
或者直接把 slot
attribute 用在一个普通元素上:
<base-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
注意: v-slot
与 slot
的用法有一个很大的区别: slot
可以用在一个普通元素上,而 v-slot
只能在template
上(当然还有一个特例,看上文)
(2)带有 slot-scope attribute 的作用域插槽
在 <template>
上使用特殊的 slot-scope attribute
,可以接收传递给插槽的 prop
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
这里的 slot-scope
声明了被接收的 prop
对象会作为 slotProps
变量存在于 <template>
作用域中。你可以像命名 JavaScript 函数参数一样随意命名 slotProps
。
这里的 slot="default"
可以被忽略为隐性写法:
<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
slot-scope attribute
也可以直接用于非 <template>
元素 (包括组件):
<slot-example>
<span slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>
slot-scope
的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript
表达式。这意味着在支持的环境下 (单文件组件或现代浏览器),你也可以在表达式中使用 ES2015 解构
,如下:
<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>
使用这里描述过的 <todo-list>
作为示例,与它等价的使用 slot-scope
的代码是:
<todo-list v-bind:todos="todos">
<template slot="todo" slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>