invokeInsertHooks
时会用到。(cbs.create是数组是因为会存在多个模块需要处理该元素(主体是模块),而vnode.data.hook.create只是用来处理自身的(主体是自己))组件在更新的时候也要重新创建? 不浪费性能吗?
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
createComponentInstanceForVnode
创建子组件实例,而后调用$mount
进行子组件的渲染。(和我们之前的文章new Vue() 整体流程对应上了是不是,整个过程两个大的步骤:实例初始化 + 渲染) // inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive) {
// keepAlive 场景,暂忽略
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
}, //... insert、prepatch、destroy
}
```
patch
的第二个场景vnode && !oldVnode会创建一个游离组件,组件等待挂载 ,还没有想到这种场景,记个TODO ❎function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
//... 创建的游离组件情况(暂时没有挂载)
}
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
```
- insert:document.insertBefore/appendChild
- keepAlive场景下,reactivateComponent,不展开。
这两个方法和snabbdom中的实现几乎完全一致,可以参考,下面重点说下patchVnode差异部分。
return
return
这里主要区别是针对组件vnode的处理:updateChildComponent
在之前创建的组件实例中,组件vue实例是保存在vnode.componentInstance中,重新渲染组件实例并不会重新创建,还是复用之前的,但是由于属性值、事件等都可能发生了变化,因此需要更新。虽然组件实例不会重新创建,但是组件标签本身关联的vnode还是会重新创建(新的vnode),并且在_render -> componentComponent 会获取最新的componentOptions,保存到vnode.componentOptions。
因此这里就是将新的vnode.componentOptions更新到oldVnode.componentInstance中。
关注一下props,vm._props
是响应式对象(initProps中增强的),这里和initProps调用validateProp
之前先设置了toggleObserving(false)
,考虑到validateProp中的有效调用是getPropDefaultValue
,难道是针对它?记个TODO ❎,有专门的提交添加此处的代码,见commit#d6bef795
注意:由于这里给vm._props
重新赋值了,因此组件中computed、watch、渲染watcher等订阅的观察者都会触发。
export function updateChildComponent (
vm: Component, propsData: ?Object, listeners: ?Object,
parentVnode: MountedComponentVNode, renderChildren: ?Array<VNode>) {
//... slot 相关,暂且忽略,后面可能小节分析
vm.$options._parentVnode = parentVnode
vm.$vnode = parentVnode // update vm's placeholder node without re-render
if (vm._vnode) { // update child tree's parent
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren
// update $attrs and $listeners hash
// these are also reactive so they may trigger child update if the child
// used them during render
vm.$attrs = parentVnode.data.attrs || emptyObject
vm.$listeners = listeners || emptyObject
// update props
if (propsData && vm.$options.props) {
toggleObserving(false)
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
// keep a copy of raw propsData
vm.$options.propsData = propsData
}
// update listeners
listeners = listeners || emptyObject
const oldListeners = vm.$options._parentListeners
vm.$options._parentListeners = listeners
updateComponentListeners(vm, listeners, oldListeners)
//... slot相关 暂且忽略
}
先发一版,后面会以一个有父子组件的demo,并记录执行状态,来更新第五节和本节的内容,并给出更多图示,比如父子组件实例,vnode的相互引用关系。