还是先上一个简单的例子,我们用例子来分析:
<script src="./vue.js"></script>
<div id="app">
<child-comp
:parent-msg = "'here is parent msg'">
</child-comp>
</div>
<script>
Vue.component('child-comp', {
template: "<div>{{parentMsg}}</div>",
props: ['parentMsg'],
mounted: function () {
console.log("parent msg is: " + this.parentMsg);
}
});
const app = new Vue({
el: '#app'
});
</script>
这个例子很简单哦,注册一个全局子组件,父组件通过props传值给子组件,子组件把这个值给渲染出来。通过之前文章(VUE源码分析之注册全局组件过程_夜跑者的博客-CSDN博客_vue源码分析)我们了解注册全局子组件过程,知道并没有对子组件实例化。那么子组件在哪里实例化的呢?
之前我们在props原理这篇文章(VUE源码分析之props原理_夜跑者的博客-CSDN博客_props原理)中分析到了创建VNode,我们接着往下看,说不定子组件实例化过程就在眼前。创建完VNode,就要把这个VNode进行渲染成真实的dom了(最终要渲染成真正的dom的),把VNode渲染成dom这个过程是通过Vue.prototype._update这个方法完成的,我们看这个方法:
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var 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.
};
先看这一行vm._vnode = vnode; 这个vnode就是我们之前创建的VNode,我们把它作为参数传给__patch__方法进行渲染。进入__patch__方法之前我们先看这两行代码:
var restoreActiveInstance = setActiveInstance(vm);
restoreActiveInstance();
var activeInstance = null;
var isUpdatingChildComponent = false;
function setActiveInstance(vm) {
var prevActiveInstance = activeInstance;
activeInstance = vm;
return function () {
activeInstance = prevActiveInstance;
}
}
代码简单,但比较巧妙。先把父组件实例原型对象设置为activeInstance,做完渲染后再恢复。这个activeInstance我们在__patch__方法中会用到。我们现在进入正题,看__patch__。
Vue.prototype.__patch__ = inBrowser ? patch : noop;
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
这两行代码可以看到__patch__方法是调用createPatchFunction方法返回的方法。传给createPatchFunction方法的参数nodeOps是一系列创建元素节点的函数:
var nodeOps = /*#__PURE__*/Object.freeze({
createElement: createElement$1,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setStyleScope: setStyleScope
});
createElement, createTextNode,是不是很熟悉。
createPatchFunction这个方法内部里面有很多内部方法,函数体最后返回了一个patch方法,看下这个patch方法:
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
var isInitialPatch = false;
var insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode
} else {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
);
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode);
}
// replacing existing element
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
var ancestor = vnode.parent;
var patchable = isPatchable(vnode);
while (ancestor) {
for (var i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor);
}
ancestor.elm = vnode.elm;
if (patchable) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, ancestor);
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
var insert = ancestor.data.hook.insert;
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
insert.fns[i$2]();
}
}
} else {
registerRef(ancestor);
}
ancestor = ancestor.parent;
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
我们之前调用__patch__如下
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
参数对起来了,通过打印我们可以看到此函数输入vm.$el 为:
<div id="app">
<child-comp
:parent-msg = "'here is parent msg'">
</child-comp>
</div>
输出为:
<div id="app">
<div>here is parent msg</div>
</div>
这个patch函数体中主要在这里实现的:
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
我们看这个createElm函数:
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode);
}
vnode.isRootInsert = !nested; // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
{
if (data && data.pre) {
creatingElmInVPre++;
}
if (isUnknownElement$$1(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
);
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode);
/* istanbul ignore if */
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
if (data && data.pre) {
creatingElmInVPre--;
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
在这个函数中我们走到createComponent这个函数,还得进去看看:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var 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
}
}
}
在这个地方看看这几行代码:
var i = vnode.data;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
vnode是我们的子组件vnode,这个vnode的data属性中是否包含hook属性,hook属性中是否包含init方法。加上打印这些条件都满足。嗯,得回到创建子组件vnode过程中怎么把hook加入的,这样我们才能找到init方法。
还记得我们创建子组件vnode过程中有个createComponent 方法吗,我们在这个函数体里面通过extractPropsFromVNodeData 方法把父组件中的props传递给了子组件。createComponent这个方法里面还有一个方法:
// install component management hooks onto the placeholder node
installComponentHooks(data);
function installComponentHooks (data) {
var hooks = data.hook || (data.hook = {});
for (var i = 0; i < hooksToMerge.length; i++) {
var key = hooksToMerge[i];
var existing = hooks[key];
var toMerge = componentVNodeHooks[key];
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook$1(toMerge, existing) : toMerge;
}
}
}
var hooksToMerge = Object.keys(componentVNodeHooks);
// inline hooks to be invoked on component VNodes during patch
var componentVNodeHooks = {
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);
}
},
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
},
insert: function insert (vnode) {
var context = vnode.context;
var componentInstance = vnode.componentInstance;
if (!componentInstance._isMounted) {
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted');
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance);
} else {
activateChildComponent(componentInstance, true /* direct */);
}
}
},
destroy: function destroy (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
} else {
deactivateChildComponent(componentInstance, true /* direct */);
}
}
}
};
我们在这里找到了init方法。我们给子组件vnode中的data属性加上了hook钩子函数。现在我们看看这个init钩子函数:
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)
}
看到了,new vnode.componentOptions.Ctor(options) 这个就是实例化子组件的地方。
还记得Ctor吗? 在创建VNode的地方:
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
这个参数{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, 就传给了VNode 的
componentOptions 属性。
实例化子组件之后还调用了 child.$mount(hydrating ? vnode.elm : undefined, hydrating); 这个是对子组件进行创建VNode,渲染成真实dom过程,父组件走的流程大体就是一样的了。
至此子组件实例化过程已分析完了。还是蛮复杂的,哪里有错误欢迎指正。