new Vue
是全局实例化一个根的Vue
实例,Vue
支持组件化,所以一个Vue
实例下会有其他子组件,子组件中也可以调用其他子组件。那么,Vue
实例和组件构造函数实例化的过程中会形成父子关系,我们可以利用$parent
和$children
的关系,让子组件有访问父组件属性和方法的能力,也让父组件有访问子组件属性和方法的能力。
1、initLifecycle
在通过new Vue
的方式进行Vue
的实例化过程中,会执行各个初始化方法,这其中包括initLifecycle
:
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
通过parent = options.parent
的方式获取父级parent
,并将其赋值给vm.$parent
。如果当前parent
存在,vm.$parent
为parent
,如果不存在,说明当前vm
即为根。
这里也定义了vm.$children
为空数组,如果当前vm
的parent
存在,那么通过parent.$children.push(vm)
的方式构建父子关系。
问题来了,这里的options.parent
是哪儿来的呢?
2、子组件init
钩子函数
组件渲染的过程中,会执行钩子函数init
:
init: function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
这里会执行createComponentInstanceForVnode
的逻辑:
function createComponentInstanceForVnode (
vnode, // we know it's MountedComponentVNode but flow doesn't
parent // activeInstance in lifecycle state
) {
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
// check inline-template render functions
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options)
}
这里构建了个options
,其中_parentVnode
就为当前的vnode
,而parent
就是传入的参数activeInstance
。
activeInstance
从哪里来?
3、_update
在子组件通过init
钩子函数渲染之前,走的渲染逻辑是Vue
原型上的_update
:
var activeInstance = null;
var isUpdatingChildComponent = false;
function setActiveInstance(vm) {
var prevActiveInstance = activeInstance;
activeInstance = vm;
return function () {
activeInstance = prevActiveInstance;
}
}
// ...
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
通过activeInstance = vm
可以看出,activeInstance
就是当前活跃的Vue
实例vm
。
这条线是已经确认了父子关系是通过parent.$children.push(vm)
构建,然后从initLifecycle
实例中的parent
开始,到子组件渲染的init
钩子函数中的activeInstance
,再到_update
中的vm
来寻找parent
的。
实际的渲染流程是,首次开始执行this._init
时,不存在parent
的值,先定义了vm.$children=[]
。子组件渲染的时候,执行到渲染逻辑_update
,这里确认了activeInstance
就是当前的Vue
实例vm
,然后,在子组件渲染的钩子函数init
中,就将activeInstance
作为createComponentInstanceForVnode
第二个参数parent
传入,并且构建options
。执行到子组件的this._init
时options
中就有了parent
,父子关系也就通过parent.$children.push(vm)
构建起来啦。