Vue源码之 slot

 比如下面这种写法

<template>
  <div> 
    <shop><div>金拱门</div></shop>
  </div>

</template>

 

金拱门三个字是显示不出来的,显示的会是shop组件的template的内容。但是我们用element-ui组件的时候,会发现很多地方是能直接显示出来的,比如el-button按钮上的文字,那么element是怎样做到的呢?

当然Vue提供了slot节点,这是第一种方式,但是element有的地方用了下面说的第二种方式,其实两种方式原理都一样,先说element的这第二种方式:

在上面的例子里,当前组件在创建真实节点的时候,组件的vm会根据template生成render函数从而生成虚拟节点,生成虚拟节点的时候,是没有普通节点和组件节点的区别的,div和shop在render函数眼里是一样的,div是根节点,子节点数组(children属性)是[shop的虚拟节点],同样的shop的虚拟节点的子节点数组是金拱门这个div。

一般情况下,shop节点会根据自己的template生成render函数,进而生成虚拟节点、真实节点,插入父节点的相应位置,如果现在想用内部的<div>金拱门</div>来代替,那么就要在生成虚拟节点的时候用可以生成金拱门的虚拟节点替换template自己的虚拟节点。

vm生成虚拟节点的时候 render函数>template>el的html,所以考虑在vm中重写render函数,比如下面的

render(f) {
      return f('div', this.$slots.default)
    }

这里的f参数的意义可以看vm._update方法中vm._render方法的源码,vnode = render.call(vm._renderProxy, vm.$createElement); 第一个参数是调用者,可以看作vm,第二个参数其实大致上就是vm._c。所以 vnode = render()相当于vnode = ()=>return createElement(vm, a, b, c, d, true)。而这里的a是tag, b是data,但是如果b传进去一个数组,这个数组会赋值给c,也就是children属性。到这里一切都通了,除了:

this.$slots.default是啥?

Vue源码6161行patch方法(这时候是在父组件的watcher的update方法中调用patch方法),在第一次创建周期的时候是从6208行的createElm一路createChildren这样找到shop节点,调用createElm来创建shop节点,因为child是个组件节点,所以进入5627行的createComponent方法,在5678行调用4213行的init方法(这个是挂载在data上的组件节点专用的初始化方法,和Vue方法中的_init方法不一样),生成shop的VueComponent对象vm,生成vm的时候,vm._init方法中有initRender方法

里面有

vm.$slots = resolveSlots(options._renderChildren, renderContext);

这句话,简单理解就是把options._renderChildren数组中的元素放入vm.$slots.default数组中

那么_renderChildren又是什么?在_init方法中,initRender之前有个initInternalComponent方法,在里面发现是parentVnode.componentOptions的children属性,parentVnode就是shop对应的vNode,

那么vnode中的componentOptions的children属性在哪?那要去vnode生成的时候的_c方法中去看,4586行的_c一直点下去,4517行,createComponent

var vnode = new VNode(
            ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
            data, undefined, undefined, undefined, context,
            { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
            asyncFactory
        );

  

对比VNode的构造函数,发现componentOptions就是{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }这个对象,children: children,这个children是createComponent的参数,

其实就是父组件生成shop节点时候的的children属性。

 再来回过头看第一种方法:slot节点写在template中或者是el挂载的节点的子节点(不会是render函数的tag,这样的话直接生成<slot>的html节点,浏览器不认识会忽略)

上面两种方法无论哪一种,都要经过$mount被重写的在vue.js最后的那一段逻辑,生成render函数也就是生成虚拟节点,这个过程中遇到tag为slot的时候会特殊处理:

compileToFunctions--->createCompiler--->var code = generate(ast, options);--->else if (el.tag === 'slot') {return genSlot(el, state)}--->在genSlot中,res = "_t(" + slotName + (children ? ("," + children) : '');,下面就是如果slot有属性,处理属性,处理属性这里也很重要,但是先不说,只看_t这是个啥,_t=renderSlot,点进去看,这里如果slot节点没有被赋值name属性,那么name就是default,先看最简单的return nodes = this.$slots[name] || fallback;又遇到大熟人this.$slots.default了,这个是包含slot节点的组件节点在父节点中的子虚拟节点数组,一切又都能说通了。

但是还有一个问题是
||fallback这个是啥?fallback是renderSlot方法的第二个参数,在genSlot方法中,是children=genChildren,简单理解的话是包含slot那个组件节点的template中,slot节点内部的元素,比如下面的

<shop><slot>肯德基</<slot></shop>肯德基这个text元素,所以,如果<shop>节点在父节点中有子节点数组,那么会在shop的template包含的slot节点替换成children数组,优先显示,如果没有,那么会显示

原来就在slot中的元素。

转载于:https://www.cnblogs.com/chuliang/p/10962331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值