概述
render函数执行以后会生成虚拟节点Vnode,Vnode以参数传入_update方法,此方法的作用就是更新或渲染真实的dom节点
vm._update(vm._render(), hydrating)
我们看一下_update方法的实现,主要是调用了__patch__方法,__patch__方法就负责生成/更新真实的dom,然后将dom对象赋值给vm.$el
// 如果是首次渲染
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 不是首次渲染
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
__patch__方法定义在runtime/index.js中
// 挂在patch方法
Vue.prototype.__patch__ = inBrowser ? patch : noop
patch
__patch__方法是由createPatchFunction方法返回的一个函数
export const patch: Function = createPatchFunction({ nodeOps, modules })
我们可以看到createPatchFunction方法很长,定义了许多方法,我们直接看最终返回的函数
此函数的主要逻辑就是区分首次渲染和更新渲染,首次渲染的话,就会调用createElm创建一个新的dom节点,如果是更新渲染的话,就会调用patchVnode对比新旧节点,然后更新dom,这就是所说的dom diff算法
// 返回patch函数,执行这个函数可以实际更新dom
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 如果新的vnode不存在,老的vnode存在,那么销毁老的vnode,然后返回undefined
// 之后的vm.$el = undefined那么界面这个元素就会被销毁
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
// 如果oldVnode未定义说明是创建
if (isUndef(oldVnode)) {
// 如果是新创建的组件 的话
// empty mount (likely as component), create new root element
isInitialPatch = true
// 创建元素
createElm(vnode, insertedVnodeQueue)
}
// 根实例会走else,因为vm.$el不为空,也就是第一次渲染会进else
else
{
// 是否是真实的dom
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// diff算法,新旧节点比对
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else
{
// 创建根组件会走这个逻辑,因为首次渲染调用vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
// 传入的是一个真实根节点
// 是真实的dom
if (isRealElement) {
/* 服务端渲染处理代码省略 */
// either not server-rendered, or hydration failed.
// create an empty node and replace it
// 创建一个空的节点
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// 创建一个新的dom节点
// create new node
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)
)
/* 非重要代码省略 */
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}