Vue3-04 插槽

Vue3学习笔记-04 插槽

插槽不是Vue3新增内容,但是对这块我一直有点模糊,所以重新学习一下

插槽内容

slot元素作为承载内容分发的出口,在slot插槽中可以包含任何模板代码或者其他组件

在组件中:

1
2
3
4
<!-- todo-button 组件模板 -->
<button class="btn-primary">
<slot></slot>
</button>

在调用时:

1
2
3
4
5
6
7
8
9
10
11
<todo-button>
<!-- 添加一个Font Awesome 图标 -->
<i class="fas fa-plus"></i>
Add todo
</todo-button>

todo-button>
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="plus"></font-awesome-icon>
Add todo
</todo-button>

如果组件中没有slot元素,那么组件起始标签和结束标签之间的内容都会被抛弃

渲染作用域

在插槽中使用数据时,例如

1
2
3
<todo-button action="delete">
Delete a {{ item.name }}
</todo-button>

插槽之间的内容与模板其余部分有相同的作用域,可以访问组件实例所有的Property

但是插槽不能访问<todo-button>的作用域,例如上面的例子中,是无法访问action的 ,因为action是被传递到<todo-button>中的,不要是在父级作用域中被定义的

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

备用内容

<slot>标签之间的内容会作为备用内容存在,当父组件中不提供任何插槽内容时,备用内容会被渲染,如果提供了内容,备用内容会被提供的内容所取代

具名插槽

<slot>元素上添加name属性,可以用来定义指定插槽的名称

1
2
3
4
5
6
7
8
9
10
11
12
<!-- base-layout -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

不带name的插槽会带有隐含的名称default

在向具名插槽提供内容时,可以在<template>元素上使用v-slot指令,参数值就是对应的插槽名称,<template>的内容就会被传入相应的插槽:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

注意,除了只有默认插槽独立存在的情况外,v-slot只能用在<template>

作用域插槽

作用域插槽的目的就是为了让插槽内容(在父组件中)访问子组件中的数据(例如上面例子中的action),这时候需要在子组件的<slot>元素上将要传递出去的数据内容作为属性来绑定:

1
2
3
<el-button type="primary">
<slot :message="message"></slot>
</el-button>

绑定在<slot>元素上的属性被称为插槽Prop,此时在父作用域上就可以使用带值的v-slot来获取插槽Prop

1
2
3
<slot-child>
<template v-slot:default="slotProps">{{ slotProps.message }} -- ok</template>
</slot-child>

此时作用域关系如图:

独占插槽的缩写语法

在被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板使用,也就是说此时v-slot可以直接加在组件上,从而省略<template>元素

1
2
3
4
<todo-list v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>

此时v-slotdefault参数也可以被省略:

1
2
3
4
5

<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>

注意,默认插槽的缩写语法不能和具名插槽混用,会导致作用域不明确,只要出现多个插槽,都需要使用基于<template>的完整语法

解构插槽Prop

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

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

所以v-slot的值实际上就是一个函数的参数,所以可以使用结构来获取插槽Prop的具体属性

1
2
3
<todo-list v-slot="{ item }">
<span class="green">{{ item }}</span>
</todo-list>

基于此,还可以提供重命名和备用内容的能力:

1
2
3
4
5
6
7
<todo-list v-slot="{ item: todo }">
<span class="green">{{ todo }}</span>
</todo-list>

<todo-list v-slot="{ item = 'Placeholder' }">
<span class="green">{{ item }}</span>
</todo-list>

动态插槽名

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

1
<template v-slot:[dynamicSlotName]="slotProps">{{ slotProps.message }} -- ok</template>

动态指令参数指的是用方括号抱起来的指令参数,它实际上是JavaScript表达式,会进行动态求值,对组件的属性和绑定的处理函数都生效

1
2
3
<a v-bind:[attributeName]="url"> ... </a>

<a v-on:[eventName]="doSomething"> ... </a>

动态参数在异常情况下值为null,用来显性的移除绑定,除此之外任何非字符串类型的值都会被触发警告

动态参数的表达式也有一些约定,例如空格和引号,在HTML的Attribute中是无效的,所以需要移除空格和引号,或者使用计算属性来替代复杂的表示

1
2
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

另外,在DOM中使用模板时(在一个HTML元素上使用动态指令)需要避免大写字母来命名键名,因为浏览器会把Attribute全部强制转换为小写:

1
2
3
4
5
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>

具名插槽的缩写

v-on缩写为@v-bind缩写为:一样,v-slot缩写为#,例如v-slot:header缩写为#header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>

<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>

和其他指令一样,这样的缩写只有在有其他参数的时候可以使用,所以单独使用#="{ item }"是无效的