了解Vue3是如何进行组件更新的
组件更新
patch
vue 在更新的时候,使用 patch 方法更新新老节点,判断老节点是否可以复用
在 patch 时会先比较新老节点的类型,比较的方式就是判断 type 和 key 是否都相等,类型不同代表不能复用,直接卸载。然后判断新节点的类型,
在这里可以看到有三种类型的节点,使用不同的方法进行处理:
- 文本节点
- Fragment 节点,vue3 引入 Fragment,在 vue3 的模板中可以使用多个根节点,会被视为 Fragment
- element(
普通元素
)类型、component(组件
)类型
component 类型
updateComponent
首先判断新老节点是否发生变化,决定需不需要更新,需要更新就执行
update,就是执行组件自己的更新流程。不需要更新就用老节点就行。通过组件的更新可以看出当父组件更新,子组件没发生变化是不会随着父组件更新的
element 类型
Vue3 通过 patchElement 方法处理 element 类型的节点,首先会去比较新老节点的 props ,如果不同就更新,然后再执行 patchChildren 进行全量比较更新。
patchChildren
在 patchChildren 中对 children 的 vnode 进行 patch,这里存在几种情况:
老节点 | 新节点 | 处理方式 |
---|---|---|
空 | 空 | - |
空 | 文本 | 添加文本节点 |
空 | 数组 | 添加多个节点 |
文本 | 空 | 删除多个节点 |
文本 | 文本 | 替换新文本 |
文本 | 数组 | 清空老节点,添加多个新节点 |
数组 | 空 | 删除多个节点 |
数组 | 文本 | 删除多个节点,添加文本节点 |
数组 | 数组 | diff |
当新老节点都是数组的时候,就会进入到 diff 的流程。
存在 key
当节点有 key 时,就会进入到 diff 流程,Vue3 通过 patchKeyedChildren
进行处理。
首先声明了一些变量
1 |
|
从头对比
从头对比就是用 isSameVNodeType
检查新老节点是否能一一相等,如果相等就 patch
,如果发现不等就 break 跳出头部对比流程。
从尾对比
从尾对比就同样用 isSameVNodeType
从尾开始遍历检查新老节点是否相等,直到遇到不等的就 break 跳出从尾对比流程
比较节点数
进行完头尾对比后会出现三种可能
- 老节点全部 patch ,新节点有剩余,说明有新增节点,就需要添加这个节点
- 新节点全部 patch ,老节点有剩余,说明有节点被删除,老节点剩余的节点不需要了,需要全部删除
- 新老节点全部有剩余,说明节点可能发生了移动,新增,删除
剩余的节点
keyToNewIndexMap
keyToNewIndexMap 是一个 [新节点的key, 索引]
的 map 集合,方便通过 key 来找到老节点的索引。
遍历老节点
这一步的主要过程就是通过老节点的 key 去查找索引,如果没有 key,就遍历剩下的新节点,看有没有和该节点相等的,如果有,就证明有可复用的老节点,直接进行 patch,如果找不到索引,就说明不可复用,直接删除,最后把新老节点对应的索引保存在 newIndexToOldIndexMap,结构是 [新节点索引, 老节点索引]
1 |
|
newIndexToOldIndexMap 是长度和新节点长度相等的,初始值都是 0 的数组。
经历过上面的步骤,老节点都已经被 patch 过了,剩下的就是新节点有新增、移动两种情况,在 newIndexToOldIndexMap 中没有找到新节点对应的值,说明是新增的节点
最长递增子序列
求出最长递增子序列,根据这个作为基准移动节点,使用最长递增子序列作为基准可以使节点移动最少。
最长递增子序列就是一个序列中,找到一个最长的子序列,并且子序列中的元素是按照递增顺序排列的。
不存在 key
没有 key 的时候会进入 patchUnKeyedChildren 来处理
首先比较老节点和新节点的 length ,获取其中较小的值,然后遍历进行 patch
然后如果老节点长度大于新节点,进行删除
如果老节点长度小于新节点,进行新增