我们知道在 HTML 中标签可以分为 闭合标签 和空标签 ,其中大多数都是闭合标签,只有少数空标签,比如:<input/>
、`<img/>
、<base/>
、<hr/>
等。在 Vue 中定义的组件也可以采用两种方式来书写。
在一个组件的 <template>
中假如显示一个标题 <h1>Hello World!</h1>
,当我们在调用时,不管是采用< 组件名 />
或者 < 组件名 ></ 组件名 >
都会显示“hello world!“,由于在闭合标签中可以包含子节点和文本,因此我们可以这样来使用< 组件名 > 我是内容 </ 组件名 >
,但是运行的结果,确什么也没有改变。
如果想要在组件传入的内容正确投射,就需要一套机制来处理,在 Vue 中自然就是 slot(插槽) 机制,在 React 中可以通过 this.props.children
来获取,而在 Angular 中可以通过 <ng-content></ng-content>
来放置转入的内容。
React 的处理方式
在 React 中传入 this.props.children
的值有三种可能:
- 没有子节点,结果为
undefined
- 子节点,结果为
object
- 多个子节点,结果为
array
如果要对传入的数据进行处理,React 还提供了一个工具方法 React.Children
来处理this.props.children
。总的来说 React 把一切处理完全交给开发人员来,完全透明。
React 的这种处理方式相对来说也更加灵活,对于 Vue 的作用域插槽机制也能很简单的实现。
Angular 的处理方式
相对于 React 的完全透明模式,Angular 则在传入内容时通过 属性 、 类(class)和 标签 三种模式来区分和限定内容的作用域。在组件内容使用 <ng-content select="xx"></ng-content>
来作为占位,具休使用方式如下:
- 传属性及属性值
<div card-body> 属性 </div>
<ng-content select="[card-body]"></ng-content>
<div card-type="body"> 属性值 </div>
<ng-content select="[card-type=body]"></ng-content>
<div card body> 多个属性值组合 </div>
<ng-content select="[card][body]"></ng-content>
- 传类(class)
<div class=".card-body"> 类 </div>
<ng-content select=".card-body"></ng-content>
<div class="card body"> 多个类组合 </div>
<ng-content select=".card.body"></ng-content>
- 传标签
<card-body></card-body>
<ng-content select="card-body"></ng-content>
- 多个插槽
<ng-content select="header"></ng-content>
<div class="body">balabala...</div>
<ng-content select="footer"></ng-content>
从上面的使用方式来看 Angular 的插槽功能还是很强大的。
Vue 的处理方式
回归到本文的主题,在 Vue 2.6.0 过后引入了新语法机制将以前版本的 slot
和scope-slot
使用一个属性来表示。具体缘由可查看其 RFC。
在新的语法中 v-slot
只允许使用在 组件 和 <template></template>
标签中,并且在只能<template>
套组件和子<template>
。
- 默认插槽
在组件中直接放一个 <slot></slot>
标签就可以接收来自组件中的内容,如果 <slot>
不为空,那么内容将作为默认值显示。
在调用时就可以使用 v-slot:default
在组件上或者其子节点 <template>
上。
- 具名插槽及缩写
具名插槽就是把分布在页面中不同位置的 <slot>
分配一个名字来标识,以区分其功能。在使用时只需要给 <slot>
添加一个 name
属性即可,例如:<slot name="footer"></slot>
。在使用时把默认插槽的 default
换成相应的名字即可,即:<template v-slot:footer></template>
。
在使用过程没有必要每次都重复写 v-slot:
,同v-on
和v-bind
一样,v-slot
也有其缩写形式,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
,即<template #footer></footer>
- 作用域插槽
作用域插槽的机制就是被调用的组件把组件内部的状态通过属性暴露给当前上下文。简单点说就是它只提供数据,至于当前数据怎么展示它不关心,类似于 React 的 Render Props 机制。
由于 Vue 官方文档写得很清楚明白这里直接上知识点:
- 解构插槽 Prop
<current-user v-slot="{user}">
{{user.firstName}}
</current-user>
- 解构时提供别名和提供初始值
<current-user v-slot="{user: person}">
{{person.firstName}}
</current-user>
<current-user v-slot="{user = { firstName:'Guest'} }">
{{user.firstName}}
</current-user>
官方提到在只有默认插槽时可以使用 插槽的缩写语法 ,将v-slot:default="slotProps"
写成 v-slot="slotProps"
,但是在使用时官方也说了 不能 和具名插槽混用,因为它会导致作用域不明确。
下面通过一个示例来实现 React 中的 Render Props 机制。
<template>
<div style="height:100%" @mousemove="handleMouseMove">
<p>The current mouse position is {{x}}, {{y}}</p>
<slot :position="position"/>
</div>
</template>
<script>
export default {
name: 'Mouse',
data() {
return {
x: 0,
y: 0
}
},
computed: {position: function() {
return {
x: this.x,
y: this.y
}
}
},
methods: {handleMouseMove(event) {
this.x = event.clientX
this.y = event.clientY
}
}
}
</script>
然后在其它组件中调用
<mouse>
<template v-slot:default="{position}">
<img src="http://iph.href.lu/64x64?text= 图片跟随鼠标" :style="{position:'absolute', left: position.x +'px', top: position.y +'px'}"/>
</template>
</mouse>
- 动态插槽
动态插槽欢迎通过编程动态控制当前组件的插槽名,使用方式如下:
<template v-slot:[dynamicSlotName]>...</template>
- 多层插槽的嵌套
<foo v-slot="foo">
<bar v-slot="bar">
<baz v-slot="baz">
{{foo}} {{bar}} {{baz}}
</baz>
</bar>
</foo>
总结
本文只是简单对 Vue 的 Slot 的知识点做一个简单的笔记,更多的知识点官方文档比较靠谱,文中并没有提到在 JSX 语法下的插槽使用方式,如果想要了解请查看我的另外一篇文章 Vue 中 jsx 不完全应用指南关于插槽部分的内容。