比如下面这种写法

  <div id='app'> 
    <shop><div>金拱门</div></shop>
  </div>

 

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

除了slot节点,还有另外一种方法:

父组件生成虚拟节点的时候,会把shop节点的信息组装成三个参数:tagName--shop,data--一个对象,children--一个数组(数组中的成员,就是调用_c或者_v这种方法返回虚拟节点,在我们这个例子里,就是<div>金拱门</div>对应的虚拟节点)

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

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

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是createComponent方法开始是传进来的参数,其实就是父组件生成shop节点时候,

shop这个虚拟节点的children---子节点数组,在本例子中,也就是以<div>金拱门</div>对应的那个虚拟节点为元素的数组。

再来回过头看第一种方法:slot节点写在template中或者是el挂载的节点的子节点

上面两种方法无论哪一种,都要经过$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节点的template是这样的:

<template><slot>肯德基</<slot></template>,fallback就是肯德基这个text元素,所以,如果<shop>节点在父节点中有子节点数组,优先显示子节点数组,如果没有,那么会显示备胎:text元素:肯德基

另:如果render函数的tag是"slot"的话直接生成<slot>的html节点,浏览器不认识会忽略