了解Vue3是如何进行组件更新的

439 阅读4分钟

组件更新

patch

当 vue 在更新的时候,使用 patch 方法更新新老节点,判断老节点是否可以复用
patch
在 patch 时会先比较新老节点的类型,比较的方式就是判断 type 和 key 是否都相等,类型不同代表不能复用,直接卸载。然后判断新节点的类型,
isSameVNodeType

在这里可以看到有三种类型的节点,使用不同的方法进行处理:

  1. 文本节点
  2. Fragment 节点,vue3 引入 Fragment,在 vue3 的模板中可以使用多个根节点,会被视为 Fragment
  3. element(普通元素)类型、component(组件)类型

component 类型

updateComponent

首先判断新老节点是否发生变化,决定需不需要更新,需要更新就执行 update,就是执行组件自己的更新流程。不需要更新就用老节点就行。通过组件的更新可以看出当父组件更新,子组件没发生变化是不会随着父组件更新的。

element 类型

Vue3 通过 patchElement 方法处理 element 类型的节点,首先会去比较新老节点的 props ,如果不同就更新,然后再执行 patchChildren 进行全量比较更新。

patchChildren

在 patchChildren 中对 children 的 vnode 进行 patch,这里存在几种情况:

老节点新节点处理方式
-
文本添加文本节点
数组添加多个节点
文本删除多个节点
文本文本替换新文本
文本数组清空老节点,添加多个新节点
数组删除多个节点
数组文本删除多个节点,添加文本节点
数组数组diff

当新老节点都是数组的时候,就会进入到 diff 的流程。

存在 key

当节点有 key 时,就会进入到 diff 流程,Vue3 通过 patchKeyedChildren 进行处理。

首先声明了一些变量

let i = 0 /* 记录索引 */  
const l2 = c2.length /* 新vnode的数量 */  
let e1 = c1.length - 1 /* 老vnode 最后一个节点的索引 */  
let e2 = l2 - 1 /* 新节点最后一个节点的索引 */  

从头对比


从头对比就是用 isSameVNodeType 检查新老节点是否能一一相等,如果相等就 patch,如果发现不等就 break 跳出头部对比流程。

从尾对比


当从头对比结束后还有剩余节点没patch,就开始从尾对比,就同样用 isSameVNodeType 从尾开始遍历检查新老节点是否相等,直到遇到不等的就 break 跳出从尾对比流程。

比较节点数

进行完头尾对比后会出现三种可能

  1. 老节点全部 patch ,新节点有剩余,说明有新增节点,就需要添加这个节点
  2. 新节点全部 patch ,老节点有剩余,说明有节点被删除,老节点剩余的节点不需要了,需要全部删除
  3. 新老节点全部有剩余,说明节点可能发生了移动,新增,删除

剩余的节点

keyToNewIndexMap


keyToNewIndexMap 是一个 [新节点的key, 索引] 的 map 集合,方便通过 key 来找到老节点的索引。

遍历老节点

这一步的主要过程就是通过老节点的 key 去查找索引,如果没有 key,就遍历剩下的新节点,看有没有和该节点相等的,如果有,就证明有可复用的老节点,直接进行 patch,如果找不到索引,就说明不可复用,直接删除,最后把新老节点对应的索引保存在 newIndexToOldIndexMap。

newIndexToOldIndexMap 结构是 [新节点索引, 老节点索引]

const newIndexToOldIndexMap = new Array(toBePatched)  
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0  

newIndexToOldIndexMap 是长度和新节点长度相等的,初始值都是 0 的数组。

经历过上面的步骤,老节点都已经被 patch 过了,剩下的就是新节点有新增、移动两种情况,在 newIndexToOldIndexMap 中没有找到新节点对应的值,说明是新增的节点。

最长递增子序列

至于需要移动的节点,首先要求出最长递增子序列,根据这个作为基准移动节点,使用最长递增子序列作为基准可以使节点移动最少。

最长递增子序列就是一个序列中,找到一个最长的子序列,并且子序列中的元素是按照递增顺序排列的。

不存在 key

没有 key 的时候会进入 patchUnKeyedChildren 来处理

首先比较老节点和新节点的 length ,获取其中较小的值,然后遍历进行 patch
然后如果老节点长度大于新节点,进行删除,如果老节点长度小于新节点,进行新增。