前言:在vue2.0 中,VNode转换成真正的DOM是通过patch(oldVNode,VNode,hydrating)方法实现的。
源码目录:/vue/src/core/vdom/patch.ts
// oldVnode 一个真实的DOM或者一个Vnode对象
// vnode 一个待替换的Vnode对象
// hydrating 是否支持服务端渲染
// removeOnly
function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) { // 如果没有新的Vnode节点
//执行destroy钩子函数
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
// isInitialPatch 是 Vue 实例的一个内部属性,用于标记组件是否进行了首次补丁(patch)
let isInitialPatch = false
// 创建子组件节点,组件vnode会被铺设到这个队列中
const insertedVnodeQueue: any[] = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
// 空挂载(可能作为组件),创建新的根元素
isInitialPatch = true
// 创建组件节点元素
// createElm 创建新节点,初始化创建函数。
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
// 如果不是真实的dom,且新旧vnode 同级比较一致
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly):负责找出差异,diff算法,DOM的更新。
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// 如果是真实的DOM节点。
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 (__DEV__) {
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.'
)
}
}
// 不是服务端渲染,或者 hydration 失败,则根据 oldVnode 创建一个 vnode 节点
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
// 获取老节点真实dom
const oldElm = oldVnode.elm
// 获取老节点父级dom
const parentElm = nodeOps.parentNode(oldElm)
// create new node
// 基于新 vnode 创建整棵 DOM 树并插入到 body 元素下
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)) {
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)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// 移除旧节点
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
// 调用insertedVnodeQueue队列中所有子组件的insert钩子
invokeInsertHook(vnode, , isInitialPatch)
return vnode.elm
}
源码逻辑分析
- 新节点不存在,旧节点存在,dom移除。
- 新节点存在,旧节点不存在,dom新增。
- 新旧节点都存在,旧节点不是真实的元素且新旧节点是同一节点,修改(更新)Diff算法。
- 新旧节点都存在,旧节点是真实的元素,一般是初始化渲染,旧节点的真实 DOM 也就是传入进来的 vm.$el 对应的元素,比如
;这里还有一个情况就是当 vnode.parent 存在,又不是同一节点,进行替换(更新,比如异步组件。