0%

vue组件深入-插槽

插槽

在 2.6.0 中,具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。

插槽内容

Vue 实现了一套内容分发的 API, 它将 <slot> 元素作为承载分发内容的出口
<slot></slot> 插槽内可以包含任何模板内容, 可以是文本、html、其他组件

如果没有使用<slot>元素, 该组件起始标签和结束标签之间的任何内容都不会渲染

1
2
3
4
5
6
7
8
9
10
11
<!-- 子组件 -->
<my-component>
<slot></slot>
</my-component>

<!-- 父组件 -->
<my-parent>
<my-component>
这里的内容会替换到子组件中slot元素的位置
</my-component>
</my-parent>

编译作用域

如果想在一个插槽中使用数据, 该插槽跟模板其他地方一样可以访问相同的实例属性(相同的作用域)

父级模板里的所有内容都是在父级作用域中编译的, 子模板里的所有内容都是在子作用域中编译的

默认值

我们在使用<slot>元素时, 可以放置一个默认内容, 当父组件中没有传入此项时, 该内容会被渲染, 当父组件有内容传入时, 传入内容替换掉默认内容, 被渲染出现

1
2
3
4
5
6
7
8
9
10
11
<!-- 子组件 -->
<my-child>
<slot>默认内容</slot>
</my-child>

<!-- 父组件 -->
<my-parent>
<my-child>
传入内容会替换掉默认内容
</my-child>
</my-parent>

具名插槽

v-slot 只能添加在<template>

有时候, 我们需要用到多个插槽, Vue 为<slot>元素提供了一个特殊的 name 属性, 可以用这个属性为额外的插槽进行定义
一个不带 name 的<slot>出口会带有隐含的名字”default”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 父组件 -->
<template>
<div class="hello">
<my-button>
<template v-slot:default>默认 name 插槽</template>
<template v-slot:title>
<h3>H3</h3>
</template>
<template v-slot:main>
<main>content</main>
</template>
</my-button>
</div>
</template>

<script>
import button from './button'
export default {
name: 'HelloWorld',
components: { 'my-button': button }
}
</script>

<!-- 子组件 -->
<template>
<div>
<slot>默认名为 default 的插槽</slot>
<p>
<slot name="title">默认内容</slot>
</p>
<slot name="main"></slot>
</div>
</template>

作用域插槽

有时候, 我们需要让插槽内容可以访问子组件中才有的数据
为了能在父级的插槽内访问到子组件的数据, 我们可以将数据作为<slot>元素的一个属性绑定上去

1
2
3
<span>
<slot v-bind:user="user">{{ user.lastName }}</slot>
</span>

绑定在<slot>元素上的 attribute 被称为插槽 prop, 我们可以在父级作用域中, 使用带值的 v-slot 来定义我们提供的插槽 prop 名字

1
2
3
4
5
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.lastName }}
</template>
</current-user>

独战默认插槽的缩写语法

当出现多个插槽时, 需要始终为所有的插槽使用完整的基于<template>语法

当被提供的内容只有默认插槽时, 我们可以不使用<template>元素, 而是将v-slot直接用在组件上

1
2
3
<current-user v-slot:default="slotProps">
{{ slotProps.user.lastName }}
</current-user>

解构插槽

作用域插槽的工作原理是将插槽内容包括在一个传入单个参数的函数里

1
2
3
function (slotProps) {
// 插槽内容
}

这意味着v-slot的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式. 所以我们可以通过解构的方式来传入具体的插槽 prop

1
2
3
<current-user v-slot:default="{user}">
{{ user.lastName }}
</current-user>

将插槽 prop 重命名

1
2
3
<current-user v-slot:default="{user: person}">
{{ person.lastName }}
</current-user>

动态插槽名

动态指令参数也可以用在v-slot上, 来定义动态的插槽名

1
2
3
4
5
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>

具名插槽的缩写

如果要使用缩写, 必须始终以明确插槽名取代

v-onv-bind一样, v-slot也有缩写, 即把参数之前的所有内容(v-slot:)替换为字符#

1
2
3
4
5
6
7
8
9
<base-layout>
<template #header>
<h1>缩写的具名插槽</h1>
</template>

<template #default>
<h1>缩写的默认插槽</h1>
</template>
</base-layout>

其他示例

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容
我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo 作为一个插槽 prop 进行绑定:

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<li v-for="todo in filteredTodos" v-bind:key="todo.id">
<!--
我们为每个 todo 准备了一个插槽,
将 `todo` 对象作为一个插槽的 prop 传入。
-->
<slot name="todo" v-bind:todo="todo">
<!-- 后备内容 -->
{{ todo.text }}
</slot>
</li>
</ul>

现在当我们使用 <todo-list> 组件的时候,我们可以选择为 todo 定义一个不一样的 <template> 作为替代方案,并且可以从子组件获取数据:

1
2
3
4
5
6
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete"></span>
{{ todo.text }}
</template>
</todo-list>
请作者喝杯咖啡