多图讲解vue3快速diff算法

vue3的diff算法名称是快速diff算法。是在vue2双端diff的基础上增加了最长递增子序列的逻辑。下面一起看下是如何实现的

整体核心思路:复用,能不动就不动,能少移动就少移动,直接创建或者直接删除。

变量说明:

c1 旧节点数组

c2 新节点数组

i 两个数组的前指针 同步向后移动 因此使用一个变量即可标识

e1 旧数组的尾指针 依次向前移动

e2 新数组的尾指针 依次向前移动

前处理

从前往后依次比较节点 直至节点不相等 源码:

css 复制代码
    // 1. sync from start
    // (a b) c
    // (a b) d e
    while (i <= e1 && i <= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
      if (isSameVNodeType(n1, n2)) {
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else {
        break
      }
      i++
    }

后处理

从后往前依次比较节点 直至节点不相等 源码:

css 复制代码
    // 2. sync from end
    // a (b c)
    // d e (b c)
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else {
        break
      }
      e1--
      e2--
    }

仅新增

假如变化前后的两组节点分别如下:仅需要处理新增即可 当i大于e1说明有新增节点 新增[i, e2]的节点内容

源码:

css 复制代码
    // 3. common sequence + mount
    // (a b)
    // (a b) c
    // i = 2, e1 = 1, e2 = 2
    // (a b)
    // c (a b)
    // i = 0, e1 = -1, e2 = 0
    if (i > e1) {
      if (i <= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i <= e2) {
          patch(
            null,
            (c2[i] = optimized
              ? cloneIfMounted(c2[i] as VNode)
              : normalizeVNode(c2[i])),
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          i++
        }
      }
    }

仅删除

假如变化前后的两组节点分别如下:仅需要处理删除即可 当i大于e2说明需要删除节点 删除[i, e1]的节点

源码:

css 复制代码
    // 4. common sequence + unmount
    // (a b) c
    // (a b)
    // i = 2, e1 = 2, e2 = 1
    // a (b c)
    // (b c)
    // i = 0, e1 = 0, e2 = -1
    else if (i > e2) {
      while (i <= e1) {
        unmount(c1[i], parentComponent, parentSuspense, true)
        i++
      }
    }

其他case(新增 删除 移动)

假如经过前面几轮比较后 当前的现状如图所示:

  1. 新建一个keyToNewIndexMap用于收集c2列表的key:index 到这个map中
typescript 复制代码
 const keyToNewIndexMap: Map<string | number | symbol, number> = new Map()
      for (i = s2; i <= e2; i++) {
        if (nextChild.key != null) {
          keyToNewIndexMap.set(nextChild.key, i)
        }
      }
  1. 遍历旧列表c1 在keyToNewIndexMap中找,如果找到了 说明可以被复用, 如果没找到说明当前节点在新列表中不存在 应该被删除。

  2. 在遍历的过程中需要创建一个newIndexToOldIndexMap的数组(叫map其实是一个数组)。数组初始化为0, 0代表节点新增。然后填充这个数组。先用旧的节点在keyToNewIndexMap中找到对应节点在新列表的下标 然后使用旧节点所在的位置 + 1 填充到newIndexToOldIndexMap对应的位置。

  3. 遍历过旧数组后删除的case 就已经被处理完毕了 ,同时我们得到了newIndexToOldIndexMap数组,记录的是新节点在旧列表中的相对顺序。然后newIndexToOldIndexMap中为 0 的就是需要新增的节点.

  4. 接下来就是找到需要移动的节点 这里我们需要让节点尽量少移动。所以寻找newIndexToOldIndexMap中的最长递增子序列。在最长子序列中的节点是不需要移动的。

当newIndexToOldIndexMap[i] === 0 说明节点是新增的 需要去挂载dom.

当newIndexToOldIndexMap[i] === increasingNewIndexSequence[j] 说明节点不需要移动。同时移动两个指针。

当newIndexToOldIndexMap[i] !== increasingNewIndexSequence[j] 说明节点需要移动

源码 patchKeyedChildren

最长递增子序列的生成方式

欢迎大家点赞收藏评论呦~

相关推荐
道一239 分钟前
在Electron应用中控制剪贴板操-复制&粘贴
前端·javascript·electron
xulihang11 分钟前
如何在Windows上使用SANE扫描文档
linux·前端·javascript
敲敲了个代码42 分钟前
11月3-5年Web前端开发面试需要达到的强度
前端·vue.js·学习·react.js·面试·职场和发展·web
半桶水专家1 小时前
Vue Pinia 插件详解
前端·javascript·vue.js
吃饺子不吃馅1 小时前
面试官:JWT、Cookie、Session、Token有什么区别?
前端·设计模式·面试
丙寅2 小时前
微信小程序反编译遇到 TypeError: _typeof3 is not a function
开发语言·javascript·ecmascript
青衫码上行2 小时前
【Java Web学习 | 第十篇】JavaScript(4) 对象
java·开发语言·前端·javascript·学习
silence_xiang3 小时前
【React】首页悬浮球实现,点击出现悬浮框
前端·javascript·react.js
沐怡旸3 小时前
【底层机制】Android内存管理技术深度解析:PMEM、ION与DMA-BUF Heaps
android·面试
隔壁的大叔3 小时前
由于vite版本不一致,导致vue组件引入报错
javascript·vue.js