上一篇讲到当与页面渲染相关的依赖发生变化时,就会触发render watcher的run方法执行,重新收集依赖,而render watcher是把updateComponent方法作为watcher的getter,因此每次页面渲染所需的数据发生改变时,都会执行updateComponent方法。
updateComponent方法很简单
updateComponent = function () {
vm._update(vm._render())
}
vm._render方法上一篇已经说过了,返回的是一个根据render函数生成的vnode对象,这里主要看一下vm._update以及相关的方法。
Vue.prototype._update = function (vnode) {
var vm = this
var prevVnode = vm._vnode
vm._vnode = vnode
if (!prevVnode) { // 首次渲染
vm.$el = this.__patch__(vm.$el, vnode)
} else {
vm.$el = this.__patch__(prevVnode, vnode)
}
}
// 挂载、更新vnode到页面上
Vue.prototype.__patch__ = function (oldVnode, vnode, parentElm, refElm) {
var isRealElement = oldVnode.nodeType
if (!isRealElement && sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode)
} else {
// 若isRealElement为true,说明传入的oldVnode为真实的dom节点,则生成一个空vnode实例,其中vnode.elm为传入的dom
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode)
}
var oldElm = oldVnode.elm
var parentElm = nodeOps.parentNode(oldElm)
createElm(vnode, parentElm, nodeOps.nextSibling(oldElm))
// 移除老的dom元素
removeVnodes(parentElm, [oldVnode], 0, 0)
}
}
// 创建真实dom,refElm为插入在哪个元素之前
function createElm (vnode, parentElm, refElm) {
var data = vnode.data
// 创建节点
if (vnode.tag) {
vnode.elm = nodeOps.createElement(vnode.tag)
// 创建子节点
createChildren(vnode, vnode.children)
// 添加attr
if (data) {
updateAttrs(emptyNode, vnode)
}
// 插入到文档中
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
大概逻辑是这样的:
1.根据vm实例上是否有_vnode属性判断是否是首次渲染,若是首次渲染,则传给patch方法的是要被挂载到的dom元素,以及当前的vnode。
2.在patch方法中,若传入的oldVnode是一个真实的dom,则为这个dom元素生成一个vnode对象(vnode.elm传入的dom节点),接下来的处理和新旧两个vnode不是同一个vnode的情况相同,插入新的vnode到文档中,并且删除旧的vnode。
附上本文详细的代码注释与demo(相关代码)