patch
主要是對新舊 vnode 進行 diff 比對,最後回傳所創建真正 的 DOM 節點完成畫面更新。
首先回顧一下,當 vm
實例需要更新時,會執行這段程式碼
// src/core/instance/lifecycle.js
vm._update(vm._render(), hydrating)
_update
function 裡,vm.__patch__
會調用 patch
function
// src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
// ...
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
// ...
}
// src/core/vdom/patch.js
return function patch (oldVnode, vnode, hydrating, removeOnly) {
/*************************************************
當新 vnode 不存在且舊 vnode 存在時,return 舊 vnode,不執行 patch
**************************************************/
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
/*************************************************
若舊 vnode 不存在
**************************************************/
if (isUndef(oldVnode)) {
isInitialPatch = true
/*************************************************
就創建一個新 vnode
**************************************************/
createElm(vnode, insertedVnodeQueue)
} else {
/*************************************************
取得舊 vnode 的 nodeType 来判斷是不是真正的 DOM
**************************************************/
const isRealElement = isDef(oldVnode.nodeType)
/*************************************************
如果不是真正的 DOM 且 `oldVnode` 和 `vnode` 是相同節點,就會進入 if 内部,執行`patchVnode`
**************************************************/
if (!isRealElement && sameVnode(oldVnode, vnode)) {
/*************************************************
`patchVnode` 就是對新舊 vnode 進行 diff 來决定要如何更新
**************************************************/
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// ...
oldVnode = emptyNodeAt(oldVnode)
}
// ...
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
/*************************************************
最後 return 新 vnode 的節點內容
**************************************************/
return vnode.elm
}
判斷 oldVnode
和 vnode
是不是相同的節點透過 sameVnode
// src/core/vdom/patch.js
if (!isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
sameVnode
判斷條件主要有兩個
都滿足以上條件,會認為是相同 vnode,就會執行 patchVnode
// src/core/vdom/patch.js
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)
)
)
)
}
// src/core/vdom/patch.js
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return
}
// ...
const oldCh = oldVnode.children
const 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(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)
}
}
cbs
是從下面這段程式碼來的
// src/core/vdom/patch.js
//
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
cbs.update
執行了下面幾個 call back,都是對當前 vnode 更新的各個階段執行對應的操作
更新完當前 vnode 後,就是對當前 vnode 的 children 做更新
// src/core/vdom/patch.js
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(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
這裡分為兩種情況
updateChildren
比較 children 的差異,這裡就是 diff 算法的核心比較到最後都是調用 createElm
創建真正的 DOM,這裡分為兩種情況
createComponent
會 return true,所以不會往下繼續,而會調用 $mount
來掛載元件// src/core/vdom/patch.js
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm))