沿用上一节的示例代码

上一节我们通过分析parse阶段和codegen流程知道了如下几点
parse阶段会向父组件节点上标记slotTarget和attrs,其中name为slot值为h1;向子组件标记了slotName为h1且tag为slot
在codegen阶段,父组件将命中attrs和slotTarget属性并将其转化为生成h1标签的_c函数的第二个参数,为{attrs:{slot:h1},slot:h1};子组件将命中tag===slot,调用函数_t,入参为slotName
那么在运行时,vue都做了什么呢
父组件
我们知道,在组件$mount的过程中将获取到我们组件在编译阶段生成的render函数并挂载到options上

之后在mountComponent过程中实例化watcher,并调用_render和update函数
那么在_render过程中将调用render函数,即执行我们上一节生成的code

这将执行_createElement去创建vnode,而data就是_c的第二个参数{attrs:{slot:h1},slot:h1},这将被标记在vnode的data属性上

同样的,p标签的vnode如下

最终对于父组件而言,将生成一个vnode,它的children是一个子组件在父组件的占位vnode

接着执行update走patch流程,根据之前的分析,这将执行组件的init过程并再次执行到initRender函数并获取slot相关的内容

options._renderChildren则保存着我们在render过程中生成的vnode

那么options._renderChildren又是从哪来的呢?
我们知道,patch的过程中会对子节点进行遍历创建调用createChildren,并将子节点的占位符vnode作为参数二传入,这里便包含了子组件每个节点对应的vnode且挂载在componentOptions属性,又,当是组件类型时会调用createComponent函数,继而走到子组件的init过程,由于本次是组件则将调用initInternalComponent进行配置合并

回到initRender函数,调用resolveSlots

框红的位置拿到我们的命名插槽,框黄的位置则保存默认插槽到default中,结果如下

接着向下执行组件的mount-render过程,在render过程中实际上是执行的我们编译阶段生成的code,即

根据之前章节的分析,_t实际上对应的函数为renderSlot

renderSlot函数体为

那么框红的$slots和$scopedSlots又是从哪来的呢?
首先在initRender过程中我们获取到了$slots,即

此时的$scopedSlots为空对象,接着在子组件的render过程中拿到占位符vnode,并调用normalizeScopedSlots

normalizeScopedSlots如下

框红的位置拿到我们的$slots进行遍历,并执行proxyNormalSlot,该函数将返回一个匿名函数,返回值为$slots.key对应的vnode,并将返回值挂载到res上以key为name返回
故,vm.$scopedSlots为,即scopedSlotFn的值

返回renderSlot,继续向下,执行scopedSlotFn拿到对应的vnode并返回

之后执行_c即_createElement函数,将我们slot对应的vnode作为子节点传入进行vnode的创建并最终挂载到组件vnode的children属性上。那么在patch阶段就会被patch为dom
因此,slot的实现的核心就是_t函数,该函数会拿到我们在占位符节点保存的slot对应的vnode节点并作为render的children传入。之后的vnode创建及patch就又都是之前分析的正常流程了
