探秘Vue插槽

结论

引用v-slot新语法

  • 新的v-slot命令联合了slotslot-scope在一个单命令语法中。
  • v-slot的简写,可以潜在地统一作用域插槽和普通插槽的使用。

基础样例

1
2
3
4
5
6
7
8
9
10
11
<!-- default slot -->
<foo v-slot="{ msg }">
{{ msg }}
</foo>

<!-- named slot -->
<foo>
<template v-slot:one="{ msg }">
{{ msg }}
</template>
</foo>

改用v-slot的诱因

Vue团队第一次推出作用域插槽时,它的使用方法很冗余,因为它老是需要使用<template slot-scope>语法。

1
2
3
4
5
<foo>
<template slot-scope="{ msg }">
<div>{{ msg }}</div>
</template>
</foo>

为了作用域插槽的使用方法更加简洁,在2.5版本里,实现了slot-scope直接在元素插槽上使用,不需要再加上template这样没有意义的标签。(这样的改进确实是一个挺好的地方,但是很快他们就后悔了。。。。😂)

1
2
3
4
5
<foo>
<div slot-scope="{ msg }">
{{ msg }}
</div>
</foo>

这样也就代表了它在组件插槽上也同样适用。(噩梦开始的地方。。。)

1
2
3
4
5
<foo>
<bar slot-scope="{ msg }">
{{ msg }}
</bar>
</foo>

上面用法就导致一个问题,slot-scope并没有很好的体现是哪一个组件提供的作用域变量,虽然slot-scope放在了bar组件中,但是它的作用域变量实际上是由foo中默认插槽提供的。特别是在组件嵌套层级很深情况下,显得尤为恶心。

1
2
3
4
5
6
7
8
9
<foo>
<bar slot-scope="foo">
<baz slot-scope="bar">
<div slot-scope="baz">
{{ foo }} {{ bar }} {{ baz }}
</div>
</baz>
</bar>
</foo>

上面的例子显然不能立刻看出来那个组件提供了那些作用域变量。

一些人建议vue团队,应该允许使用slot-scope在组件自身上,以此来显示它默认插槽的作用域。

1
2
3
<foo slot-scope="foo">
{{ foo }}
</foo>

根据上面的介绍,很明显这个功能实现不了,因为当具有插槽的组件嵌套时很容易导致歧义。

1
2
3
4
5
<parent>
<foo slot-scope="foo"> <!-- provided by parent or by foo? -->
{{ foo }}
</foo>
</parent>

这就是为啥,vue团队后悔在使用slot-scope时可以不使用template.

那么问题就来了为什么不直接修改slot-scope语法,而是提出一个新的vue指令?

  • 这是一个突破性的改变,不能在2.x版本发布。
  • 即使在3.x版本中发布,然而这样会导致大量面向Google编程的程序员的困惑(因为Google出大量已经过时的资料🤣)。
  • 在3.x版本中,计划统一solt类型,这样就没必要区分,作用域插槽,普通插槽啦。比如一个插槽可能会接受props也可能不会,但是这都是插槽,但是使用slotslot-scope两个不同的属性,显得不必要。所以出现要一个统一的语法是大势所趋。

细节设计

新的指令终于诞生了v-slot

它可以被用在template插槽容器上,以表示传递给组件的插槽,其中插槽名称通过指令参数表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<foo>
<template v-slot:header>
<div class="header"></div>
</template>

<template v-slot:body>
<div class="body"></div>
</template>

<template v-slot:footer>
<div class="footer"></div>
</template>
</foo>

v-slot作用域插槽,可以通过命令的属性值来接收slot props,同时v-slot的值和slot-scope有同样的运行方式,都支持解构语法。

1
2
3
4
5
6
7
<foo>
<template v-slot:header="{ msg }">
<div class="header">
Message from header slot: {{ msg }}
</div>
</template>
</foo>

v-slot可以直接被用于组件上,不需要任何参数来表明组件的默认插槽是作用域插槽,而且传向默认插槽的变量和没有做声明的插槽传入的变量是同样的效果。

1
2
3
<foo v-slot="{ msg }">
{{ msg }}
</foo>

新神VS旧神

对大多数作用域插槽的使用(单个默认插槽),仍然提供简洁的语法

1
<foo v-slot="{ msg }">{{ msg }}</foo>

新的语法提供更明确的作用域变量,和插槽的关系。再来看一下用slot-scope,v-slot深层嵌套的的比较。

1
2
3
4
5
6
7
8
9
<foo>
<bar slot-scope="foo">
<baz slot-scope="bar">
<div slot-scope="baz">
{{ foo }} {{ bar }} {{ baz }}
</div>
</baz>
</bar>
</foo>
1
2
3
4
5
6
7
<foo v-slot="foo">
<bar v-slot="bar">
<baz v-slot="baz">
{{ foo }} {{ bar }} {{ baz }}
</baz>
</bar>
</foo>

默认插槽中具有文本

1
2
3
4
5
6
7
8
9
10
11
12
<!-- old -->
<foo>
<template slot-scope="{ msg }">
{{ msg }}
</template>
</foo>

<!-- new -->
<foo v-slot="{ msg }">
{{ msg }}
</foo>

默认插槽和元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- old -->
<foo>
<div slot-scope="{ msg }">
{{ msg }}
</div>
</foo>

<!-- new -->
<foo v-slot="{ msg }">
<div>
{{ msg }}
</div>
</foo>

嵌套默认插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- old -->
<foo>
<bar slot-scope="foo">
<baz slot-scope="bar">
<template slot-scope="baz">
{{ foo }} {{ bar }} {{ baz }}
</template>
</baz>
</bar>
</foo>

<!-- new -->
<foo v-slot="foo">
<bar v-slot="bar">
<baz v-slot="baz">
{{ foo }} {{ bar }} {{ baz }}
</baz>
</bar>
</foo>

具名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- old -->
<foo>
<template slot="one" slot-scope="{ msg }">
text slot: {{ msg }}
</template>

<div slot="two" slot-scope="{ msg }">
element slot: {{ msg }}
</div>
</foo>

<!-- new -->
<foo>
<template v-slot:one="{ msg }">
text slot: {{ msg }}
</template>

<template v-slot:two="{ msg }">
<div>
element slot: {{ msg }}
</div>
</template>
</foo>

嵌套混合具名与默认插槽

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
<!-- old -->
<foo>
<bar slot="one" slot-scope="one">
<div slot-scope="bar">
{{ one }} {{ bar }}
</div>
</bar>

<bar slot="two" slot-scope="two">
<div slot-scope="bar">
{{ two }} {{ bar }}
</div>
</bar>
</foo>

<!-- new -->
<foo>
<template v-slot:one="one">
<bar v-slot="bar">
<div>{{ one }} {{ bar }}</div>
</bar>
</template>

<template v-slot:two="two">
<bar v-slot="bar">
<div>{{ two }} {{ bar }}</div>
</bar>
</template>
</foo>

缺点

  • 引入新的语法会带来混乱,并使生态系统中有关此主题的许多学习材料过时。新用户可能会在阅读现有教程后发现新语法而感到困惑。
  • 默认的槽使用v-lot=“{msg}”并不能准确地传达这样的概念,即msg是作为prop传递给槽的。

注意

  • 注意 v-slot 只能添加在 <template>,(只有一种例外情况) 。

  • 当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。

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

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

<template v-slot:other="otherSlotProps">
...
</template>
</current-user>

扩展

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。

我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo 作为一个插槽 prop 进行绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
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>

翻自:

https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md

https://cn.vuejs.org/v2/guide/components-slots.html#ad