patch的意思是打补丁,包含一种在原有基础上修改的意思。但是Vue的patch包含了三层意思:创建、修改、删除。目前先走马观花看一下patch过程。
这里有个基本概念sameVnode
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)
)
}
function sameInputType (a, b) {
if (a.tag !== 'input') { return true }
var i;
var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;
var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;
return typeA === typeB
}
虽然名字是sameVnode,但是判断标准相对较松,翻译成值得比较节点比较合适。它要求两个节点的tag一致、key一致、如果是input还要type一致,如果满足这些条件,就认为可以考虑在原有基础上修改。
创建
当旧vnode是真实DOM时,这意味着我们是处于生命周期的mount阶段。我们需要将新vnode映射为真实DOM,并且替换旧DOM。
更新
这对应生命周期的update阶段。当新旧vnode不值得比较时,会直接创建新的DOM替换掉旧DOM。
当新旧两个节点值得比较时,会调用patchVnode更新DOM,这是patch的重点。
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) {
return
}
// once这样能重复利用就重复利用
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
vnode.elm = oldVnode.elm;
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 elm = vnode.elm = oldVnode.elm;
var oldCh = oldVnode.children;
var ch = vnode.children;
// update vnode相关属性
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)) {
// 新vnode和老vnode都有子节点,updateChildren处理
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
} else if (isDef(ch)) {
// 只有新vnode有子节点,添加对应节点即可
if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
// 只有旧vnode有子节点,干掉这些子节点
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); }
}
}
patchVnode是从vnode更新视图的核心方法。首先处理了once这样的特殊情况,保证复用DOM元素,然后更新data属性,接着处理子节点。如果新vnode有子节点而旧vnode没有子节点,直接添加对应节点;如果没有新子节点只有旧子节点,干掉旧子节点即可;最为复杂的情况是新旧vnode均有子节点,那么需要调用updateChildren方法,关于这个方法,请看aooy的博客。
销毁
当没有新的vnode只有旧的vnode时,意味着我们要销毁旧vnode,这时候调用invokeDestroyHook销毁旧节点,这对应生命周期的destroy阶段。