组件的初始化渲染
在之前render的时候,如果createElement
的第一个参数tag
是一个组件,就会调用createComponent
创建组件的VNode,那么接下来看组建的patch过程有什么不一样的地方
createPatchFunction
中的createElm
函数有一个分支逻辑:
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
那么看一下createComponent
的定义:
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
}
}
}
如果vnode.data.hook.init
是有定义的,那么调用这个init
函数:init(vnode, false)
。这个init
函数是创建组件VNode时添加的钩子函数init
,prepatch
,insert
,destory
之一。
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
if
分支的逻辑是keep-alive
的情况,else
分支首先调用了createComponentInstanceForVnode
,它用new vnode.componentOptions.Ctor
创建了一个VueComponent实例,Ctor是通过Vue.extend
继承Vue构造函数得到的。
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
return new vnode.componentOptions.Ctor(options)
}
这个组件实例的options
中有几个特别的属性:_isComponent: true
表示它是个组件,_parentVnoode
是组件VNode,parent
是当前的activeInstance
。 组件的构造函数调用了Vue.prototype._init
方法
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// ...
initLifecycle(vm)
// ...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
由于options._isComponent === true
,这里执行了initInternalComponent
方法,这个方法把子组件的一些配置合并到子组件的$options
中。在调用initLifecyle
时,把子组件实例添加到了parent.$children
中。最后由于vm.$option.el
没有定义,$mount
在这里是不会被调用的。
回到componentVNodeHooks
里的init
钩子函数,createComponentInstanceForVnode
之后调用了child.$mount(undefinded, false)
,这个$mount
就是Vue.prototype.$mount
, 它最终调用了core/instance/lifecycle.js
中的mountComponent
方法来挂载这个子组件实例,最后调用了Vue.prototype._init
方法
vm._update(vm._render(), hydrating)
我们知道组件的根节点只有一个,所以vm._render()
执行了组件的render
函数,返回组件根节点的VNode。然后调用_update
方法
export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
// ...
const prevActiveInstance = activeInstance
activeInstance = vm
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
}
这个activeInstance
是定义在core/instance/lifecycle.js
的全局变量,export let activeInstance: any = null
。作用是保存当前激活的Vue实例。实际上Vue的初始化是一个深度优先遍历的过程,每一个子组件都需要知道他的父组件$parent
是什么,activeInstance
用来保存当前vm的父实例。在_update
的时候,用prevAcitveInstance
保存上一次的activeInstance
,_update
结束后再恢复,这样就可以在深度优先遍历的过程中保存组件实例之间的父子关系。
_update
中又执行了patch
的过程,如果又遇到子组件就重复上面的逻辑。
渲染后的组件根DOM结点保存到vm.$el
。返回到createElm
中的createComponent
:
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
}
}
}
···
调用`init`钩子之后,`vnode.componetInstaence`就应该是一个组件的Vue实例了,调用`insert`将组件的DOM结点插入DOM