scope

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
喜欢就支持一下吧
点赞0 分享