Vue指南的要点笔记(七)

本篇的要点是:插槽。 Vue3.0要出来了,2.0曾经的插槽语法已被废弃,现在是全新的知识点。

插槽的基本认识

自定义组件会经常遇到模板内容不固定的场景,插槽可以帮助我们把不固定的模板内容,变为灵活的形式,这样在使用自定义组件的其它文件中,就能根据特定逻辑的需求使用新的模板内容。插槽通过slot这个元素来定义,假设有一个navigation-url的自定义组件,它的模板内容可能是这样的:

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

其中的slot表示为一个插槽。当外部使用navigation-url组件的时候,navigation-url标签之间的内容就会取代组件定义中slot位置进行渲染:

1
<navigation-url>查看主页</navigation-url>

查看主页这个文本会替换掉组件原有定义中的slot的位置。navigation-url标签之间可以是任何形式的有效的Vue模板内容。如果在自定义组件里面没有slot定义,那么在使用自定义组件时,自定义组件标签之间的所有内容都会被忽略。

自定义组件有自己的模板,所以有自己的作用域;外部使用自定义组件的时候,可能想要使用自定义组件所拥有的作用域的数据,比如这样的:

1
2
3
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
</navigation-link>

但这样是不行的。url是自定义组件的一个prop数据,在使用自定义组件的时候,它的标签之间的模板内容,其实是访问不到自定义组件实例内部的作用域数据的。这是因为它标签之间的模板内容,不是与自定义组件定义的模板一起编译的,而是与使用自定义组件标签的模板一起编译的,Vue规定:

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
所以自定义组件标签之间的模板内容,能够访问到自定义组件标签所在的整体模板中的作用域数据,但是访问不到自定义组件实例本身的数据。

比如这样,假设user是自定义组件使用时所在模板中的一个数据:

1
2
3
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>

这样是可以的。

自定义组件定义中的slot可以包含模板内容:

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
<slot>Summit</slot>
</a>

这样当外部使用这个组件,且组件标签之间没有内容的时候,Submit会成为这个slot的默认替换内容。 slot之间也可以是任意形式的有效的模板内容,且能正常访问到组件实例的数据。

具名插槽

复杂的自定义组件,可能会有多个部分的模板是灵活的:

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>

这个时候就需要在组件中使用多个插槽,前面学习的单一插槽在使用的时候不会有任何歧义,但是当组件有多个插槽的时候,就会有歧义了,外部使用的时候怎么知道要把外部的模板内容跟哪个插槽对应起来呢?所以给插槽起名字就有必要了,这种带名字的插槽就是具名插槽。每个自定义组件最多只能有一个匿名插槽,也就是默认插槽,但是可以有多个具名插槽。给插槽起名字需要用到name属性。

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

那个匿名的默认插槽,有一个隐含的名字: default,在使用插槽其它特性时候,可能会用到这个名称。

外部使用组件的时候,可以在组件标签之间,通过template标签和v-slot来向指定的插槽传入替换的模板内容:

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template v-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 v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

  • v-slot后面接的是组件定义内部的slot的名称
  • 任何没有被包裹在带有 v-slottemplate 中的内容都会被视为默认插槽的内容。

要传入默认插槽的内容,也可以用一个template包裹起来,然后使用default这个隐含名称:

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

v-slot只能添加在template标签上,只有一个例外,就是后面要说的独占默认插槽。

作用域插槽

前面说过,自定义组件标签之间的内容,会被传入插槽,但是这部分模板无法访问到组件实例的作用域数据,这显然是不满足需求的,所以要想办法在插槽的替换模板中拿到组件实例的作用域数据才行。

为了让插槽的替换模板内容,能够访问到特定的组件实例数据,必须在组件定义的时候,将外部可能要用到的数据,绑定到slot标签上:

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

绑定在 <slot> 元素上的特性被称为插槽 prop。现在在父级作用域中,我们可以给 v-slot 绑定一个值来定义我们提供的插槽 prop 的名字:

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

这里的slotProps就是给前面v-bind:user="user这个数据起的名字,在这个插槽的替换模板中,可以用这个名字来访问组件内部给插槽绑定的user数据。上面的例子同时还用到了default这个隐含名称。slot标签上可以通过v-bind绑定多个数据,slotProps可以访问到全部绑定的数据。

独占默认插槽的缩写语法

当自定义组件只有一个插槽的时候,自定义组件的标签可以当成template来用,v-slot指令可以直接写在自定义组件标签上,这个地方就是前面说的那个v-slot指令使用的例外。

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

由于这是一个默认插槽,所以v-slot还可以省略掉default这个参数:

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

v-slot作用于自定义组件标签,同时省略default参数的写法, 仅限于自定义组件内部只有一个插槽的情况。如果包含多个插槽,vue官方建议始终使用完整的作用域插槽的语法。

插槽prop解构

插槽的替换模板内容,内部原理,是转换为一个传入单一参数的函数:

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

所以v-slot后面绑定的插槽prop,可以是任意能作为函数参数的js表达式。所以插槽prop,非常适合使用ES6的对象结构和数组结构的语法:

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

重命名:

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

默认值:

1
2
3
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>

动态插槽名

动态参数也支持在v-slot这个指令中使用,所以插槽可以是动态的模板:

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

这个特性有没有价值,就看实际工作有没有它的相关需求了。

具名插槽的缩写

为了简化具名插槽的写法,类似v-bindv-on都有简写形式一样,v-slot也有简写。 可以把v-slot:替换为一个单一的#号来写,如:

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

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

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

所有的指令在没有参数的时候使用简写,都会弹出警告:

1
2
3
4
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>

如果想继续使用简写,就必须提供一个名称作为参数:

1
2
3
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>