react-diff、vue2 diff、vue3 diff

2
open
sqshada
sqshada
Posted 4 months ago

react-diff、vue2 diff、vue3 diff #23

Diff的瓶颈以及React如何应对

diff 缺陷

diff 操作本身也会带来性能损耗,将前后两棵树完全比对的算法的复杂程度为 O(n^3 ),其中 n 是树中元素的数量

react 优化

  • 同级元素进行 diff,如果跨越了层级,React 将不会尝试复用
  • 不同类型的元素会产生不同的树。如果元素由 div 变成了 p,React 会销毁 div 及其子孙节点,新建 p 及其子孙节点
  • 可以通过 tag、key 来判断元素在不同的渲染下保持稳定

diff 分为两类

  • 当 newChild 类型为 object、number、string,代表同级只有一个节点
  • 当 newChild 类型为 Array,同级有多个节点

单节点 diff

  • key 和 type 相同表示可以复用
  • key 不同直接标记删除节点,然后创建新节点
  • key 相同 type 不同,标记删除该节点和兄弟节点,然后创建新节点

多节点 diff

​ 在源码中多节点 diff 会经历三次遍历

  • 第一次遍历处理节点的更新(包括props更新和type更新和删除)
  • 第二次遍历处理其他的情况(节点新增),其原因在于在大多数的应用中,节点更新的频率更加频繁
  • 第三次处理位节点置改变

第一次遍历

  • key 不同,遍历结束
  • newChildren 或者 oldFiber 遍历完,遍历结束
  • key 相同 tag 不同,标记 oldFiber 为 DELETION
  • key 相同 type 相同,标记可以复用

newChildren 遍历完,oldFiber 没遍历完,在第一次遍历后将 oldFiber 中没遍历完的节点标记 DELETION(即删除的 DELETION Tag)

第二次遍历

  • newChildren 和 oldFiber 全部遍历完,diff 结束
  • newChildren 没有遍历完,oldFiber 遍历完了,将剩下的 newChildren 标记为 Placement(即插入的 Tag)
  • newChildren 和 oldFiber 都没有遍历完,进入节点移动的逻辑

第三次遍历

sqshada
sqshada
Created 4 months ago

vue2 diff 双端比较

  • 使用旧列表的头一个节点oldStartNode与新列表的头一个节点newStartNode对比
  • 使用旧列表的最后一个节点oldEndNode与新列表的最后一个节点newEndNode对比
  • 使用旧列表的头一个节点oldStartNode与新列表的最后一个节点newEndNode对比
  • 使用旧列表的最后一个节点oldEndNode与新列表的头一个节点newStartNode对比

当四次对比都没找到复用节点时,我们只能拿新列表的第一个节点去旧列表中找与其 key 相同的节点。

  • 当我们在旧列表中找到对应的 VNode,我们只需要将找到的节点的 DOM 元素,移动到开头就可以了
  • 在旧列表中没有找到复用节点,直接创建一个新的节点放到最前面就可以了,然后后移头指针 newStartIndex。
sqshada
sqshada
Created 4 months ago

Vue3 Diff —— 最长递增子序列

借鉴 inferno ,有两个理念。

  • 第一个是相同的前置与后置元素的预处理;
  • 第二个则是最长递增子序列,此思想与React的diff类似又不尽相同。

前置后置预处理后,剩余部分进入 diff。
判断是否需要移动:准备一个 source 数组,预先填充 -1,将新节点在旧节点的位置保存在 source 中,如果旧节点在新列表中没有,直接删除,如果新列表 patch 完毕,旧列表剩余部分可以直接删除。
遍历后在 source 中为 -1 的节点即为新节点。
根据 source 找到最长递增子序列,