在上一章中我们讲解并实现了渲染器的挂载逻辑,本质上就是将各种类型的
VNode渲染成真实DOM的过程。渲染器除了将全新的VNode挂载成真实DOM之外,它的另外一个职责是负责对新旧VNode进行比对,并以合适的方式更新DOM,也就是我们常说的patch。本章内容除了让你了解基本的比对逻辑之外,还讲述了在新旧VNode比对的过程中应该遵守怎样的原则,让我们开始吧!
# 基本原则
通常重渲染(re-render)是由组件的更新开始的,因为在框架的使用层面开发者通过变更数据状态从而引起框架内部对UI的自动更新,但是组件的更新本质上还是对真实DOM的更新,或者说是对标签元素的更新,所以我们就优先来看一下如何更新一个标签元素。
我们首先回顾一下渲染器的代码,如下:
function render(vnode, container) {
const prevVNode = container.vnode
if (prevVNode == null) {
if (vnode) {
// 没有旧的 VNode,使用 `mount` 函数挂载全新的 VNode
mount(vnode, container)
// 将新的 VNode 添加到 container.vnode 属性下,这样下一次渲染时旧的 VNode 就存在了
container.vnode = vnode
}
} else {
if (vnode) {
// 有旧的 VNode,则调用 `patch` 函数打补丁
patch(prevVNode, vnode, container)
// 更新 container.vnode
container.vnode = vnode
} else {
// 有旧的 VNode 但是没有新的 VNode,这说明应该移除 DOM,在浏览器中可以使用 removeChild 函数。
container.removeChild(prevVNode.el)
container.vnode = null
}
}
}
如上高亮的两句代码所示,当使用 render 渲染器渲染一个全新的 VNode 时,会调用 mount 函数挂载该 VNode,同时让容器元素存储对该 VNode 对象的引用,这样当再次调用渲染器渲染新的 VNode 对象到相同的容器元素时,由于旧的 VNode 已经存在,所以会调用 patch 函数以合适的方式进行更新,如下代码所示:
// 旧的 VNode
const prevVNode = h('div')
// 新的 VNode
const nextVNode = h('span')
// 第一次渲染 VNode 到 #app,此时会调用 mount 函数
render(prevVNode, document.getElementById('app'))
// 第二次渲染新的 VNode 到相同的 #app 元素,此时会调用 patch 函数
render(nextVNode, document.getElementById('app'))
patch 函数会对新旧 VNode 进行比对,也就是我们所说的 diff,那么不同的两个 VNode 之间应该遵守怎样的比对规则呢?其实这个问题很容易回答,我们知道 VNode 有类型之分,不同类型的 VNode 之间存在一定的差异,所以不同的 VNode 之间第一个比对原则就是:只有相同类型的 VNode 才有比对的意义,例如我们有两个 VNode,其中一个 VNode 的类型是标签元素,而另一个 VNode 的类型是组件,当这两个 VNode 进行比对时,最优的做法是使用新的 VNode 完全替换旧的 VNode
