找回密码
 立即注册
首页 业界区 业界 [vue3] vue3更新组件流程与diff算法

[vue3] vue3更新组件流程与diff算法

忙贬 3 天前
在Vue3中,组件的更新通过patch函数进行处理。
patch函数

源码位置:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)
  1. const patch: PatchFn = (
  2.     n1,
  3.     n2,
  4.     container,
  5.     anchor = null,
  6.     parentComponent = null,
  7.     parentSuspense = null,
  8.     namespace = undefined,
  9.     slotScopeIds = null,
  10.     optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren,
  11. ) => {
  12.     // 二者相同,不需要更新
  13.     if (n1 === n2) {
  14.         return
  15.     }
  16.     // vnode类型不同,直接卸载旧节点
  17.     if (n1 && !isSameVNodeType(n1, n2)) {
  18.         anchor = getNextHostNode(n1)
  19.         unmount(n1, parentComponent, parentSuspense, true)
  20.         n1 = null
  21.     }
  22.         // ......
  23.     const { type, ref, shapeFlag } = n2
  24.     switch (type) {
  25.         case Text:
  26.             // 处理文字节点
  27.             break
  28.         case Comment:
  29.             // 处理注释节点
  30.             break
  31.         case Static:
  32.             // 静态节点
  33.             break
  34.         case Fragment:
  35.             // Fragment节点
  36.             break
  37.         default:
  38.             if (shapeFlag & ShapeFlags.ELEMENT) {
  39.                 // 处理普通DOM元素
  40.             } else if (shapeFlag & ShapeFlags.COMPONENT) {
  41.                 // 处理组件
  42.             } else if (shapeFlag & ShapeFlags.TELEPORT) {
  43.                 // 处理teleport
  44.             } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  45.                 // 处理suspense
  46.             } else if (__DEV__) {
  47.                 // 报错:vnode类型不在可识别范围内
  48.                 warn('Invalid VNode type:', type, `(${typeof type})`)
  49.             }
  50.     }
  51. }
复制代码
patch函数用来挂载或者更新vnode。
patch的大致流程:

  • n1和n2如果相等,则表示无变化,直接退出;
  • n1和n2如果引用不同,则先检查其vnode类型,如果类型不同,则直接卸载n1,挂载n2
  • 主流程:根据n1和n2的vnode类型,调用不同的process函数。
process

process函数的参数列表大致相同,都是要传入n1、n2和container等参数。patch函数主要起到一个分类讨论的功能。
这里只讨论普通元素类型组件类型的vnode处理过程,因为这是Vue应用中最常见、覆盖范围最广的两种类型。
普通元素类型,即ShapeFlags.ELEMENT,在浏览器环境下就是指DOM类型。
普通元素vs组件

从组件树的角度来理解普通元素和组件元素的区别。
1.png
一个组件的children可以是普通元素或组件元素。

  • 叶子节点必须是普通元素,因为只有普通元素能够通过相关平台挂载到界面上。Vue会在编译时确定mount方法,以适应不同的平台。对于浏览器环境来说,普通元素是通过vnode来表示DOM节点,将vnode转换成实际DOM元素并插入到页面上的操作由vue3源码中的runtime-dom这个package实现。
    叶子节点必须是普通元素,但是普通元素不一定是叶子节点,比如一个div标签内部可以包含其它组件。

  • 叶子节点不可能是组件,因为组件必须被实现且被注册,其实现必须使用已注册的组件或者普通元素。并且组件是虚拟元素,并不能被实际挂载到指定平台上,只能递归地patch它的children,直到把普通元素都挂载到界面上。
在 Vue3 - patch 函数的源码中可以看到除了这两种类型,还有很多针对其它类型 vnode 的 process 函数,这些 process 函数主要做的只有两件事:挂载和更新。
对于旧vnode n1 和 新vnode n2:

  • 当n1是null时,则表示挂载n2;
  • 当n1不为null时,则表示n1更新为n2;
patchElement

patchElement 会对它的 children 也进行 patch,也就是调用 patchChildren 函数。
children 有三种情况:文本、数组、NULL。
2.png

diff算法

diff 算法用于将旧children的vnode数组更新为新children的vnode数组,它通过比较两个序列,尽可能地复用相同的vnode,以此来减少频繁创建vnode带来的开销。
事实上,diff 是在 patchKeyedChildren 中实现的,对于没有设置 key 的数组,patchChildren函数内部调用的是 patchUnkeyedChildren,函数实现大致如下:

  • 计算两个数组的长度的最小值 commonLength;
  • 前 commonLength 个 vnode 直接 patch 更新,不会考虑移动到不同位置来复用;
  • 旧序列如果有剩余则unmount,新序列如果有多余则mount。
这种做法在大多数情况下都会需要创建 vnode,开销还是比较大的。因此为了提高渲染性能,使用渲染列表的时候要写上 key。
3.png
vue3 的 diff算法实现在patchKeyedChildren函数中,主要包含五个流程,其中第五个是最复杂的步骤:

  • 两个序列从头部向尾部依次同步,直到不能匹配进入下个流程;
    起始索引都是从0开始,用一个变量 i 就可以了。
    4.png

  • 两个序列从尾部向头部依次同步,直到不能匹配进入下个流程;
    两个序列长度可能不一样,最后一个元素的索引不一样,因此需要两个变量 e1 和 e2 来指向 ending index。
    5.png

在上述两个流程之后:
<ol start="3">如果旧序列遍历完了,而新序列还有剩余,则新序列剩余的vnode依次mount;
<ul>i>e1则表示旧序列遍历完了;
i
您需要登录后才可以回帖 登录 | 立即注册