vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
createElm
在之前解析patch
(点击查看)的时候,我们在createElm
中我们已经见过了createComponent
。
在createElm
中会先尝试着调用createComponent
来确定是不是一个组件,如果是组件就进入createComponent
的逻辑,如果不是组件就继续向下。所以我们这次要进入createComponent
来看看具体的逻辑。
createComponent
(patch.js)function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data;
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
//判断是否有data.hook以及data.hook.init并调用
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);
//在DOM上插入组件
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
}
}
一开始是keep-alive的逻辑,我们跳过,进入一个判断
//判断是否有data.hook以及data.hook.init并调用
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */);
}
这个判断的意思是如果vnode.data
中有hook
且有init
的话,就调用init
这个hook
,hook
的赋值详见:Vue源码解析系列——组件篇:createComponent的执行过程。
进入init
hook
init
hookinit(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);
}
}
一开始是keep-alive的逻辑,直接跳过看else:
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
调用createComponentInstanceForVnode
,返回一个组件实例。
createComponentInstanceForVnode
进入createComponentInstanceForVnode
:
export function createComponentInstanceForVnode(
// we know it's MountedComponentVNode but flow doesn't
vnode: any,
// activeInstance in lifecycle state
parent: any
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode, //父组件的vnode
parent, //activeInstance,当前vm的实例
};
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
//实例化组件,顺便调用_init_()
return new vnode.componentOptions.Ctor(options);
}
首先是定义一个配置,_isComponent: true
,_parentVnode: vnode
将父组件的vnode赋值给_parentVnode
,parent:parent
,这个parent
其实就是activeInstance
,activeInstance
定义在lifecycle.js
中,我们一会看。
这个方法最后new
了一个组件的实例,调用了构造函数,这个构造函数的定义:
const Sub = function VueComponent(options) {
this._init(options);
};
上一篇有讲,不再过多赘述。
进入_init
:
_init
if (options && options._isComponent) {
//对组件option的合并
// 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的合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
由于上面定义了_isComponent
为true
,所以进入initInternalComponent
:
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
const opts = (vm.$options = Object.create(vm.constructor.options));
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode;
opts.parent = options.parent; //activeInstance,当前vm的实例,也就是组件的父vm实例
opts._parentVnode = parentVnode; //占位符vnode
const vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
其实就是一些options的合并。
回到_init
,继续向下,运行initLifecycle(vm);
,进入:
export function initLifecycle(vm: Component) {
const options = vm.$options;
// locate first non-abstract parent
let parent = options.parent;
//父组件的$children入栈
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent;
}
parent.$children.push(vm);
}
//将$parent指向options.parent
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;
}
这边其实也是对options的一些处理,屡清楚了一些父子关系。
在lifecycle.js
中我们会看到一个函数:
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance;
activeInstance = vm;
return () => {
activeInstance = prevActiveInstance;
};
}
结合下面的_update
中的:
const restoreActiveInstance = setActiveInstance(vm);
//渲染vnode
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
/**
* 深度优先patch
* 第一次生成的时候第一个参数是一个真实的dom
* 之后调用pathc就全部使用vnode了
*/
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
//将activeInstance还原成记录中的上一个activeInstance
//深度优先一层一层还原
restoreActiveInstance();
我们可以发现,这个activeInstance
其实就是当前激活的vm
,由于__patch__
是一个深度优先的方法,所以这个activeInstance
会一层一层的深度赋值,最后又使用了restoreActiveInstance
一层一层还原回父级的vm
。以一种巧妙的方式屡清了组件的父子关系。
好了,回到_init
:
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
由于组件是没有el
的,所以这边的$mount
并不会触发,那组件是在哪里挂载的呢?
我们再回到init
hook,发现,在createComponentInstanceForVnode
之后立即进行了组件的挂载:
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
//挂载子组件
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
于是,这就形成了一个递归,$mount
又会调用patch
,又会执行上面说的所有步骤。
最后执行完init
hook之后,又回回到createComponent
(patch.js)方法:
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);
//在DOM上插入组件
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
调用方法initComponent
,之后把组件insert
至真实的dom。
进入initComponent
:
function initComponent(vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(
insertedVnodeQueue,
vnode.data.pendingInsert
);
vnode.data.pendingInsert = null;
}
//将patch后的dom赋值给vnode.elm以便于insert方法插入至dom
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);
}
}
这里的关键代码在vnode.elm = vnode.componentInstance.$el;
,将组件实例的$el
赋值给vnode.elm
,这样insert
就可以正常插入组件了。
至此,组件的patch
过程分析完毕。
最后奉上一个自己制作的流程图。