scope & slot parse
vue
中的 作用域插槽经历了多个版本的迭代,不同版本的使用方式和使用限定范围(主要指标签)也有所不同,再开始解析之前,不妨先来以时间为轴,探究下各个时期的作用域插槽的使用。
开始之前要先明确,非作用域插槽只是指定了插槽的名称,所以在 v2.6
之前,它始终没有变化。
tip:按照规则,不存在 slot="name"
属性的标签默认指定的是 slot="default"
scope 的使用演变
scope 时代
这个时期的作用域插槽的指令是 scope
。只能用于 template
元素上。
<template slot="head" scope="row">
<h1>scope - {{row.name}}</h1>
</template>
复制代码
slot-scope 时代
这个时代的特点是自由,具名插槽指令可以遍布在所有已知的组件上。
<self-component slot-scope="row" slot="header"></self-component>
<div slot-scope="row" slot="header"></div>
<template slot-scope="row" slot="header"></template>
复制代码
v-slot 时代
这个时代的特点是简便,将作用域和插槽名称的指定合并在一个指令 v-slot
之上。
<div v-slot:head="row"></div>
<!--非作用域插槽-->
<div v-slot:head></div>
复制代码
卸载最后的非作用域插槽
<div slot="header">header</div>
复制代码
分析
第一步:插槽的本质
在了解怎么解析之前,需要简要了解下插槽的本质是什么,这样子才能方便后续的进一步解析,以及解析的目标。
需要明确的是
- 对于一个非作用域插槽,需要知道这个插槽的名字以及这个插槽的
render
函数。这就是非作用域插槽的最终解析目标。 - 对于一个作用域插槽,不仅需要知道名称和 render 函数,还需要知道 标识作用域的变量名称。
有上面的分析可知,对于非作用域插槽:
<div slot="header">header</div>
复制代码
需要的做种代码可能如下:
const slot = {
header: () => {
return // 插槽对应的 vnode
}
}
复制代码
自然而然的,对于作用域插槽而言:
<template v-slot:header="scope">
<div>{{scope.header}}</div>
</template>
复制代码
期望的最终解析结果是
const slotScope = {
header: function(scope){
return // 插槽对应的 vnode
}
}
复制代码
从上面的分析中可以发现,这里需要解析出的是:
- 插槽名称
header
- 插槽作用域对象
scope
(作用域插槽才需要)
但是由于存在历史版本,在解析时应该将所有情况包裹在内
parse
function getAndRemoveAttr(el, name) {
if(el.attrMap[name] != null) {
const attrList = el.attrList
for(let i = 0; i < attrList.length; i++){
if(attrList[i].name === name) {
attrList.splice(i, 1)
return el.attrMap[name]
}
}
}
}
// 环境变量标识 v-slot 是否可以使用
process.env.NEW_SLOT_SYNTAX = true
const slotRE = /^v-slot(:|$)|^#/
function parse(el) {
let slotScope
// 解析作用域变量名
if(el.tag === 'template'){
el.slotScope = getAndRemoveAttr(el, 'scope') || getAndRemoveAttr(el, 'slot-scope')
} else if(slotScope = getAndRemoveAttr(el, 'slot-scope')){
el.slotScope = slotScope
}
// 解析插槽的名称
const slotTarget = getAndRemoveAttr(el, 'slot')
if(slotTarget){
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
}
/* 2.6 及其以后的版本 */
if(process.env.NEW_SLOT_SYNTAX){
if(el.tag === 'template'){
// v-slot on <template>
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
const { name, dynamic } = getSlotName(slotBinding)
el.slotTarget = name
el.slotTargetDynamic = dynamic
el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
}
} else {
// v-slot 即使绑定在了 组件上,内容也会被重新放到一个 template 中
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
const slots = el.scopedSlots || (el.scopedSlots = {})
const { name, dynamic } = getSlotName(slotBinding)
// 将 <compoentName v-slot="xxxx">dsdd</>
// 转换为 <component><template v-slot="xxx">dsdd</template></component>
const slotContainer = slots[name] = createASTElement('template', [], el)
slotContainer.slotTarget = name
slotContainer.slotTargetDynamic = dynamic
slotContainer.children = el.children.filter((c: any) => {
if (!c.slotScope) { // 作用域插槽中不能再包含其他的作用域插槽,不然会被忽略
c.parent = slotContainer
return true
}
})
slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
el.children = []
el.plain = false
}
}
}
}
复制代码
代码解析
上述代码兼容了vue各个时代的作用域和非作用域插槽的解析,其实可以看到,对于 v-slot
定义的作用插槽菜而言,不论模板内容是否在 template
中包裹,最后都会将插槽内容包含在template
标签中。
总结
通过分析可知,在 vue
中,使用作用域和非最作用域插槽的方式是在 template
标签上使用。
对于 scope 而言
- 只能在
template
标签上使用 - 使用
slot=name
指定名称
对于 slot-scope 而言
- 可以在任意合法标签上使用
- 使用
slot=name
指定名称
对于 v-slot 而言
- 只能在
template
和 组件 上使用- 在组件上使用时,依然会将其移动到创建的
template
标签上去
- 在组件上使用时,依然会将其移动到创建的
- 使用
v-slot:name="scope"
来指定名称和作用域变量
由于语法变更的次数较多,所以在使用时尽量统一使用一种语法,方便维护。对于动态插槽名称,也有两种版本的使用方式
:slot="name"
,name
是动态名称v-slot[dynamicSlotName]"
或者使用缩写#[dynamicSlotName]
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END