# patch
/src/core/vdom/patch
- patch 操作基本都在
createPatchFunction中
# createPatchFunction
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; i++) {
const evtName = hooks[i]
cbs[evtName] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][evtName)) {
cbs[evtName].push(modules[j][evtName])
}
}
}
// ...
return function patch(oldVnode, vnode, hydrating, removeOnly)
}
// 首次 vm.__patch__(vm.$el, vm._vnode, hydrating, false/* removeOnly */)
// vm.__patch__(prevNode, vnode)
function patch(oldVnode, vnode, hydrating, removeOnly) {
// 如果新 vnode 不存在,而 olaVNode 存在,则销毁 oldVnode
// vm.$sestory => vm.__patch__(vm._vnode, null)
if (isUndef(vnode)) {
// 触发 oldVnode 里 所有chidlren 里 destroy 里的 hook 事件
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// 组件的初次渲染
isInitialPatch = true
// 会将 vnode 放到 insertedVnodeQueue
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 不是真实的 DOM 但是新老节点是同一个节点,执行 patch
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// vm.__patch__(vm.$el, vm._vnode, hydrating, false/* removeOnly */) Vue 根组件的首次渲染
if (isRealElement) {
// 处理服务端渲染
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttributte(SSR_AUTH)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, isertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env,NODE_ENV !== 'production') {
warn('...')
}
}
// 不是服务端渲染,或者 hydrating 失败,则根据 oldVnode 创建一个 vnode 节点
// 通过 vm.$el 生成一个空 oldVnode
oldVnode = new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
// 拿到老节点的元素
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// 基于新 vnode 创建整颗 DOM 并插入到 parentElm 元素
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 递归更新父占位符节点元素
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
const insert = ancestor.data.hook.insert
if (insert.merged) {
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// 没有 parent 代表不在 dom 树中了
if (isDef(parentElm)) {
// 运行 oldVnode 的 destroy
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
// 运行 oldVnode 的 destroy
invokeDestroyHook(oldVnode)
}
}
}
// 将 insertedVnodeQueue 触发 insert 回调函数
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
# invokeDestroyHook
function invokeDestroyHook(vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
# sameVnode
function sameVnode (a, b) {
// key 必须要相同, 标签相同,是否注释节点 是否都有 data 属性
return (
a.key === b.key && (
// 标签相同
a.tag === b.tag &&
a.siComment === b,isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
// 异步占位符节点
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
}