preface
Last time we looked at the process from vNode creation to generation. Now let’s explore how Vue converts vNodes into real DOM nodes/elements
Vue.prototype._update
The _render function we mentioned last time was actually passed in as an argument to the _update function, in other words, after the _render function ends, _update will execute 👇
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.
};
Copy the code
Here’s the logic of the code:
- call
setActiveInstance(vm)
Set the currentvm
Is an active instance - judge
preVnode
If yes, callvm.$el = vm.__patch__(prevVnode, vnode);
, otherwise callvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
(Essentially the difference between the first rendering and the second update) - call
restoreActiveInstance()
Reset an active instance - right
HOC
I made a special judgment (because I didn’t use itHOC
, so skip it.)
The setActiveInstance function returns only a closure function (which is not important). If you need to know more, how is the __patch__ function implemented
Other relevant codes:
updateComponent = function () { vm._update(vm._render(), hydrating); }; . new Watcher(vm, updateComponent, noop, {before: function before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate'); }}},true /* isRenderWatcher */);
Copy the code
__patch__
It may surprise you, but the __patch__ function implementation is actually quite simple 👇
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); . Vue.prototype.__patch__ = inBrowser ? patch : noop;Copy the code
Obviously, createPatchFunction also returns a closure function
patch
Although __patch__ looks very simple, but in fact the internal implementation of the logic is quite complex, the amount of code is also very much 👇
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 if(process.env.NODE_ENV ! = ='production') {
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
}
Copy the code
With so much code, it’s certainly too much to digest at once, so we can take a tentative look at 👇 with the following questions
- For the first time
patch
Operation and subsequentpatch
What is the difference between operations? dom
What are the rules when a change is made between nodes, or when “new nodes” replace “old nodes”?
patch
The special logic of a function
Patch function has special logic for initial rendering. Obviously, we only need to go through the logic of patch for the first time to make it clear 👇
Combined with the above source code, summed up the idea here:
- If old node is empty, it is called
createElm(vnode, insertVnodeQueue)
To create a new node directly. - If the old node exists
dom
Node is divided into the following steps:- Remove the “old node”
SSR_ATTR
Attribute (if present) - Determine if it is “rendering” (
hydrating
)- Is to perform
hydrate(oldvnode, vnode, insertVnodeQueue)
Then check whether the command is executed successfully- Trigger on success
invokeInsertHook(vnode, insertVnodeQueue, true)
- Issue “warning” after failure (test environment)
- Trigger on success
- Otherwise the call
emptyNodeAt(oldVnode)
, to the “old node” (actuallydom
Node) generated it.”vnode
“
- Is to perform
- Remove the “old node”
A “forgotten” line of code
After reading the source code, it is not difficult to find that the above combed logic is missing this code:
if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly);
}
Copy the code
That is to do the operation of patchVnode once for “the same node of non-DOM element”. This code can be broken down into several points:
- What is “same node”?
patchVnode
What did you do?
“Same Node”
According to the semantics, we should look at this part of the code 👇
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
Copy the code
The logic of sameVnode is to determine whether two VNodes are the same node according to the attributes of vNodes
patchVnode
Since the premise of executing patchVnode is that the old and new nodes are “the same” node, we have reason to believe that patchVnode is used to deal with the changes of the same node.
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode);
}
var elm = vnode.elm = oldVnode.elm;
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
} else {
vnode.isAsyncPlaceholder = true;
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance;
return
}
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
var oldCh = oldVnode.children;
var ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
if(isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }}if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
} else if (isDef(ch)) {
if(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(ch);
}
if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ' '); }
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, ' '); }}else if(oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text); }if (isDef(data)) {
if(isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }}}Copy the code
Let’s look at what this code does:
- reuse
vnode
(If presentelem
Attributes) - Working with asynchronous components
- Working with static nodes
- perform
prepatch
(If presentdata
Attributes) - perform
update
(If presentdata
Attributes) - To compare
oldVnode
å’Œvnode
The two nodes - perform
postpatch
(If presentdata
Attributes)
Of course, the most intuitive here is to compare the logic of oldVnode and vnode 👇
The rest of the logic can be left for the next article
Scan the QR code below or search for “Teacher Tony’s front-end cram school” to follow my wechat official account, and then you can receive my latest articles in the first time.