这篇文章简单介绍一下由虚拟Dom创建真实Dom的过程,还是以一个简单例子来进行分析:
<html>
<head>
<style type="text/css">
</style>
</head>
<body>
<script src="./vue.js"></script>
<div id="app">
<div>{{message}}</div>
</div>
<script>
const app = new Vue({
data : {
message : "Vnode -> node"
}
}).$mount("#app");
</script>
</body>
</html>
之前文章分析过创建虚拟Dom的过程,这里看看从虚拟Dom到真实Dom过程。
// public mount method
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
function mountComponent (
vm,
el,
hydrating
) {
console.log("liubbc vm.$el: " + el.outerHTML);
vm.$el = el;
vm.$el 这个变量后边会用到,先看看这个变量的打印:
liubbc vm.$el: <div id="app"> <div>{{message}}</div> </div>
updateComponent = function () {
vm._update(vm._render(), hydrating); //vm._render()会返回新创建Vnode
};
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 就是前面打印的div元素节点
console.log("liubbc vm.$el: " + vm.$el.outerHTML);
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.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) vm.$el 作为oldVnode,vnode是以vm.$el新创建的虚拟Dom 。

新创建的vnode 里面能看到message: "Vnode -> node" 。
下面就要看看vm.__patch__这个函数了。
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop;
return function patch (oldVnode, vnode, hydrating, removeOnly) {
console.log("oldVnode type is: " + oldVnode); //[object HTMLDivElement]
console.log("oldVnode is: " + oldVnode.outerHTML); //<div id="app">
// <div>{{message}}</div>
//</div>
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); //div 的 nodeType === 1
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); //在这里把真实的Div元素节点转换成了虚拟Dom节点
}
console.log("oldVnode processed type is: " + oldVnode); // [object Object]
// replacing existing element
var oldElm = oldVnode.elm; //oldElm 就是转换成虚拟dom节点之前的真实dom节点
var parentElm = nodeOps.parentNode(oldElm);
console.log("parentElm is: " + parentElm.outerHTML); //oldElm的父节点元素就是
//body节点元素
// create new node 这个函数用来由虚拟dom节点创建真实dom节点,
//并把新节点挂载到parentElm节点下
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 {
console.log("liubbc createElm registerRef")
registerRef(ancestor);
}
ancestor = ancestor.parent;
}
}
// destroy old node
if (isDef(parentElm)) {
//删掉原来的节点({{message}})
removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
在关键代码处加了注释。把原来的div节点({{message}}) 先转换成虚拟dom,用新虚拟dom创建真实dom(Vnode -> node),并挂载到body节点下。最后把原来的节点删掉掉。
再看看虚拟dom创建真实dom具体过程:
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); //创建真实Dom节点
setScope(vnode);
/* istanbul ignore if */
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
//在这里遍历create hooks,之前文章分析过register ref过程
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);
}
}
|