Vue高级用法插槽原理深入分析(中)

243 阅读2分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

具名插槽

往往我们需要灵活的使用插槽进行通用组件的开发,要求父组件每个模板对应子组件中每个插槽,这时我们可以使用<slot>name属性,同样举个简单的例子。

var child = {
  template: `<div class="child"><slot name="header"></slot><slot name="footer"></slot></div>`,
}
var vm = new Vue({
  el: '#app',
  components: {
    child
  },
  template: `<div id="app"><child><template v-slot:header><span>头部</span></template><template v-slot:footer><span>底部</span></template></child></div>`,
})

渲染结果:

<div class="child"><span>头部</span><span>底部</span></div>

模板编译的差别

父组件在编译AST阶段和普通节点的过程不同,具名插槽一般会在template模板中用v-slot:来标注指定插槽,这一阶段会在编译阶段特殊处理。最终的AST树会携带scopedSlots用来记录具名插槽的内容

{
  scopedSlots: {
    footer: { ··· },
    header: { ··· }
  }
}


AST生成render函数的过程也不详细分析了,我们只分析父组件最终返回的结果(如果对parse, generate感兴趣的同学,可以直接看源码分析,编译阶段冗长且难以讲解,跳过这部分分析)

with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',
{scopedSlots:_u([{key:"header",fn:function(){return [_c('span',[_v("头
部")])]},proxy:true},{key:"footer",fn:function(){return [_c('span',[_v("底
部")])]},proxy:true}])})],1)}

很明显,父组件的插槽内容用_u函数封装成数组的形式,并赋值到scopedSlots属性中,而每一个插槽以对象形式描述,key代表插槽名,fn是一个返回执行结果的函数。

父组件vnode生成阶段

照例进入父组件生成Vnode阶段,其中_u函数的原形是resolveScopedSlots,其中第一个参数就是插槽数组。

// vnode生成阶段针对具名插槽的处理 _u      (target._u = resolveScopedSlots)
  function resolveScopedSlots (fns,res,hasDynamicKeys,contentHashKey) {
    res = res || { $stable: !hasDynamicKeys };
    for (var i = 0; i < fns.length; i++) {
      var slot = fns[i];
      // fn是数组需要递归处理。
      if (Array.isArray(slot)) {
        resolveScopedSlots(slot, res, hasDynamicKeys);
      } else if (slot) {
        // marker for reverse proxying v-slot without scope on this.$slots
        if (slot.proxy) { //  针对proxy的处理
          slot.fn.proxy = true;
        }
        // 最终返回一个对象,对象以slotname作为属性,以fn作为值
        res[slot.key] = slot.fn;
      }
    }
    if (contentHashKey) {
      (res).$key = contentHashKey;
    }
    return res
  }

最终父组件的vnode节点的data属性上多了scopedSlots数组。回顾一下,具名插槽和普通插槽实现上有明显的不同,普通插槽是以componentOptions.child的形式保留在父组件中,而具名插槽是以scopedSlots属性的形式存储到data属性中。

// vnode
{
  scopedSlots: [{
    'header': fn,
    'footer': fn
  }]
}