React原理:通俗易懂的 diff 算法

2,676 阅读3分钟

React 新老架构的 diff 对比

React 16 之前,React 是直接通过 递归遍历 VDOM 树 查找不同,对有变化的部分重新生成真实DOM。

React 16之后,引入了 Fiber 架构,在 Reconciler(协调器)中会进行 diff 算法,流程大致如下:

  • 第一次渲染时,不需要 Diff 。直接将 VDOM 转 Fiber,内存中构建 workInProgressFiber 树,构建完之后,用它替换 currentFiber,再通知 Renderer(渲染器)渲染。
  • 后续更新渲染时,会将新生成的 VDOM 和 旧的 Fiber 进行对比,决定生成怎样的新的 Fiber(能复用的节点复用,多余的删除,新增的新增)。完成后对新生成的 Fiber 进行 DOM 操作。

那么,React 16 之后的 diff 具体是怎么对比的呢?

diff 具体流程

我们直接举例子来分析 diff 的流程。

比如现在有一个父节点,它的子节点为 A、B、C、D,那么生成的 VDOM 如下:

image.png

因为是第一次渲染,被 Reconciler 直接转成 Fiber:`

image.png

然后渲染。

这时候,组件更新了,新的 VDOM 是 A、C、E、F 后,如何处理呢?

diff 会分成两次遍历

  1. 第一次 让新的 VDOM 和 旧的 Fiber 进行对比,看有没有能复用的节点,如果有,继续遍历,如果没有,就停止遍历。
  2. 判断新的 VDOM 有没有遍历完,如果遍历完,把旧的 Fiber 中剩下的节点删掉即可。若新的 VDOM 没有遍历完,则进行第二次遍历。
  3. 第二次遍历时,把 旧的 Fiber 中剩下的 节点,放入一个 Map,然后遍历 新的VDOM 剩下的节点,看当前遍历的 VDOM 有没有存在于这个 Map 里面,若存在,则表明可以复用,打上更新的标记。
  4. 遍历完 新的 VDOM 后,旧的 Fiber 剩下的节点打上删掉标记,新的 VDOM 中新增的节点打上新增标记

以上就是 React 16后,Reconciler 过程中 diff 的过程。

下面直接图解上述过程:

第一轮遍历:

线性一一对比,当前 新VDOM 的节点A 和 旧的 Fiber 中的节点A 可以复用,打上更新标记,然后继续遍历 新VDOM 的下一节点C,发现节点C 和 Fiber 中的 节点B 不能复用,结束遍历。

image.png

此时 新VDOM 的节点还没遍历完,则进行第二次遍历

第二轮遍历:

  1. 接下来,把旧的 Fiber 中,剩下的 节点 B、C、D 放入一个 Map,key 就是节点的 key。
  2. 继续遍历 新VDOM 中剩下的节点,同样去找能不能复用的节点。比如发现只有 C 节点能在 Map 中找到,则打上更新标记。

遍历完毕后:

  1. Map 中剩下的 B、D 节点打上删除标记
  2. 新VDOM 中的 E、F 节点打上新增标记

图解如下: image.png

知道了节点如何变化后,生成新的 Fiber 如下:

image.png

至此,我们完成了 Diff。

总结

React 16后的 diff 核心就是 查找复用节点

第一轮遍历时,线性一一对比,若 新VDOM 的当前节点 和 旧的 Fiber 当前节点不能复用,则终止遍历。

第二轮遍历时,将 旧的 Fiber 剩余节点放入 Map,继续遍历 新的VDOM 中的节点,寻找复用节点,打上更新标记。遍历完毕后,Map中剩下的节点打上删除标记,新的VDOM 中,没找到复用的节点打上新增标记。

最后根据变化,生成新的 Fiber,然后 Commit 阶段渲染。

以上就是 React 16 Fiber 架构下的 Diff 算法。

结尾

以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论