精读 Vue 官方文档系列 ?
插槽 Slot
其设计灵感源自 Web Components 规范草案
,将 <slot>
元素作为承载分发内容的出口。
关于内容分发,我们可以类比于 $attrs
实例属性分发所有的 Attribute;$listeners
实例属性可以分发所有的事件一样。
插槽作用在组件的内部,用来接收自定义元素(自定义组件)开始标签与结束标签之间的内容,然后在组件内容指定位置进行输出,如果自定义组件的模板不含有 <slot>
元素,那么自定义元素的内容将会被抛弃。
Vue.component('custom-element', {
template:'<p></p>'
});
Vue.component('custom-element-2', {
template:'<p><slot></slot></p>'
});
复制代码
<!-- This is custom element content 并不会显示 -->
<custom-element>This is custom element content</custom-element>
<!-- 正常显示 -->
<custom-element-2>This is custom element content</custom-element-2>
复制代码
插槽可以接受任何类型的内容,也包括另一个组件。
编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
插槽的内容定义在父级,编译在父级,属于父级的一部分,编译后的内容会传入到插槽元素所在的组件中由该组件执行后展示,遵循于 JavaScript 词法作用域的规定,插槽内容只能访问父级作用域里面的数据,而不能访问插槽元素所在子组件内的数据。
后备内容
插槽的默认值会在插槽没有接收到任何内容时默认被渲染。
Vue.component('base-button', {
template:'<slot>button</slot>'
});
复制代码
父级会将内容放置在自定义元素的开始与结束标签之间来传递值,然后在自定义组件内部中通过
<slot>
元素来分发内容。
具名插槽
当需要多个插槽来分发内容时,可以为 <slot>
元素命名,用来锚定内容输出所对应的插槽。
顾名思义,“具名插槽”就是存在多个插槽时为插槽命名以作区别。
使用 name
属性为多个插槽命名,一个不带 name
的出口会带有隐含的名字“default”。
<script id="base-layout-template"></script>
<script>
Vue.component('base-layout', {
template:'#base-layout-template',
});
</script>
复制代码
实际使用时我们会通过 <template>
元素进行分组,每个 <template>
元素上绑定一个 v-slot
指令,并以插槽名称作为指令参数,来为锚定的具名插槽提供一块完整的内容输出。
<base-layout>
<p>Content</p>
<template v-slot:header>Header & Menu & Nav</template>
<template v-slot:footer>Footer</template>
</base-layout>
复制代码
图示:
作用域插槽
从插槽的编译作用域可知,定义在父组件作用域中的插槽内容是无法获取插槽元素 <slot>
所在组件内的数据。具有作用域的插槽就是为了打通插槽由内到外的数据传递,让插槽内容也能访问插槽元素 <slot>
所在组件内的数据。
步骤如下:
- 使用
v-bind
指令来为当前组件内的<slot>
元素绑定插槽 Prop,这个 Prop 编译后就会作为参数(组件内的数据)传递到作用域插槽中。 - 在父作用域的插槽内容
<template>
元素上使用v-slot
指令并以插槽名称作为指令参数来接收上一步<slot>
元素所绑定的插槽 Prop,从而实现在插槽中获取插槽所在组件内的数据。
Vue.component('mouse-move', {
template:'<slot name="default" v-bind:pos="{x:xAxis, y:yAxis}"></slot>,
data(){
return {
xAxis:0,
yAxis:0
}
},
mounted(){
window.addEventListener('mousemove',e=>{
this.xAxis = e.clientX;
this.yAxis = e.clientY;
},{passive:true});
}
})
复制代码
<mouse-move>
<template v-slot:default="slotProps">
<ul>
<li>xAxis:{{slotProps.x}}</li>
<li>yAxis:{{slotProps.y}}</li>
</ul>
</template>
</mouse-move>
复制代码
如果存在多个具名插槽,分别单独接收每个插槽绑定的 Prop 即可,默认的插槽名称为 default
或者直接为空。
<slot-example>
<template v-slot="slotProps">{{slotProps.value}}</template>
<template v-slot:other="otherSlotProps"> {{otherSlotProps.value}}</template>
</slot-example>
复制代码
独占默认插槽的写法
若组件只提供了一个默认插槽,那么便可以直接将 v-slot=”slotProps“
指令添加在组件的自定义元素上。
<slot-example v-slot="slotProps"></slot-example>
复制代码
等价于
<slot-example>
<template v-slot:default="slotProps"></template>
</slot-example>
复制代码
可以实现省去一个 <template>
元素的效果。
如果组件内部存在多个插槽,那么插槽内容必须要严格使用 <template>
元素并添加 v-slot:[slotName]="{{slotName}}SlotProps"
方式锚定需要输出的目标具名插槽。
<slot-example>
<template v-slot="slotProps"></template>
<template v-slot:other="otherSlotProps"></template>
</slot-example>
复制代码
注意默认插槽的名称为
default
,可省去不写。
解构插槽
插槽内容是在父级中编译,编译后的插槽内容会被包裹在一个拥有单个参数的函数里,再传递到子组件中,由子组件执行,当子组件执行时会同插槽 Prop 的值一同传入。
Vue 会将 <slot>
元素上绑定的所有 Props 以键值对的形式组合在 slotProps
对象中,然后作为参数传递给包裹插槽内容的方法里,这意味着,我们可以通过 ES6 的解构语法在模板中以更简洁的方式取得插槽 Prop 中的值。
<slot-example v-slot="{x, y}"></slot-example>
复制代码
动态插槽名称
具名插槽的名称可以是一个动态的值,这与动态指令参数的效果相同。
<!--Definition-->
<slot :name="slotName" :values="vals"></slot>
<!--Usage-->
<slot-example>
<template v-slot:[slotName]="slotProps">
</template>
</slot-example>
复制代码
具名插槽简写
与 v-on
,v-bind
指令一样,我们可以使用 #
来缩写 v-slot
指令
<template #default></template>
<template #other="otherSlotProps"></template>
复制代码
缩写
v-slot
指令时必须具有指令参数。
其它应用
当我们进行可复用组件设计时,既想基于子组件绑定的插槽Prop来渲染出不同的内容,又想让父组件也可以自定义部份布局,那么使用”作用域插槽“的模式将会很有效果。
下面是一些基于“作用域插槽”这思想实现的可复用的 Vue 组件。
总结
使用 <slot>
元素的 name
属性来定义一个具名插槽,其中 name 的值可缺省,默认为 default ; 插槽的 <slot>
元素还可以通过 v-bind
指令将插槽所在组件内的数据绑定到 Prop 中,在插槽内容被执行时作为参数传递进入。
在父作用域中使用具有插槽元素的组件时,便可以通过为其绑定 v-slot:[name]="slotProps"
指令来接收对应具名插槽绑定的 Prop。
#
可用于替代v-slot
指令进行缩写,并且必须含有指令参数。- 插槽默认名称为
default
,可省略不写。