Note: Reading this article requires a clear understanding of the PATCH process of VUE. If you are not clear about the patch process, it is recommended to understand this process before reading this article, otherwise you may feel foggy.
Let’s do a scene before we talk
<div id="app">
<aC />
<bC />
</div>
Copy the code
As shown above, the app.vue file has two child components that are brothers to each other
Created and Mounted are two lifecycle hooks (A) and C are short for CREATED
/ / a component
created() {
console.log('aC')},mounted() {
debugger
console.log('aM')},/ / b component
created() {
console.log('bC')},mounted() {
debugger
console.log('bM')},Copy the code
What is the order of printing? You can imagine it and see if it works.
Readers who are familiar with the vue patch process may think that the sequence is aC→aM→bC→BM, that is, component A is created first, then mounted, and then the above process is repeated in component B. As can be seen from the patch method, after the component is created, the process of insert into the parent container is a synchronous process. Only after this process is completed, it will traverse to component B and go through the rendering process of component B.
In fact, the browser prints aC→bC→aM→bM, that is, two created are executed first and then mounted, which contradicts the above analysis. Mounted is not called immediately after a child component is created. Insert mounted is not called immediately after a child component is created. The following analysis from the source point of view:
Patch calls createElm to create a real element. CreateElm uses createComponent to determine whether the current VNode is a component
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 */);
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
// After the component is created, it will go here to insert the component's EL into the parent node
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true}}}Copy the code
CreateComponent inserts the component’s EL into the parent node, which is returned to the patch call stack and called
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
Copy the code
The child component has vnode.parent so it goes one branch, but let’s see what the second branch calls insert
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue;
} else {
for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); }}}Copy the code
This insert is attached to vnode.data.hook, and there is a call to the createComponent method during component creation
InstallComponentHooks, where the INSERT hook is injected. Callback callback (componentInstance, ‘Mounted ‘); this method is defined in the componentVNodeHooks object. Insert calls callHook(componentInstance,’ Mounted ‘).
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 */); }}},Copy the code
Take a look at this method, children go first branch, just carried out a line of code vnode. Parent. Data. PendingInsert = queue, the queue is actually at the beginning of the patch, define insertedVnodeQueue. The logic here is to attach the current insertedVnodeQueue to pendingInsert of the parent’s vNode data.
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue;
} else {
for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); }}}InsertedVnodeQueue is defined as an empty array at the beginning of patch
var insertedVnodeQueue = [];
Copy the code
InsertedVnodeQueue insertedVnodeQueue initComponent is called in createComponent
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
vnode.elm = vnode.componentInstance.$el;
if (isPatchable(vnode)) {
// ⚠️ pay attention to this method
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 hookinsertedVnodeQueue.push(vnode); }}Copy the code
InsertedVnodeQueue. Push. Apply (insertedVnodeQueue, vnode. Data. PendingInsert) focus on this line of code, Vnode.data. pendingInsert pushes each entry into the insertedVnodeQueue of the current vnode using apply. So instead of pushing the list of vNode.data. pendingInsert, push each item in the array. That is, the component collects items from its child insertedVnodeQueue, because rendering is a deeply recursive process, The insertedVnodeQueue of all the last root components gets every entry in the insertedVnodeQueue of all the children.
Insert (queue[I].data.hook. Insert (queue[I])) insertedVnodeQueue Insertedvnodequeue. push: invokeCreateHooks push the current vNode.
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) { i.create(emptyNode, vnode); }
// Push the current vNode to insertedVnodeQueue
if(isDef(i.insert)) { insertedVnodeQueue.push(vnode); }}}Copy the code
For component VNode, this method is still called in initComponent.
Mounted hook is inserted into the parent vnode insertedVnodeQueue. The parent vnode insertedVnodeQueue is inserted into the parent vNode. The parent vnode insertedVnodeQueue is inserted into the parent vNode. Mounted hook is called by vNode’s INSERT hook. The mounted of the deepest child component is executed first. Finally, a source debug diagram is attached. You can clearly see what the insertedVnodeQueue of the root component is.
As to why the vue to this design, because the mount is a first son of father, child component to insert to the parent node, but the parent node is not really inserted into the page, if child components mounted, immediately call for users of the framework may cause confusion, because the child component calls mounted it doesn’t really apply colours to a drawing into the page, And you certainly can’t manipulate the DOM by document.querySelector.