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 如下:
因为是第一次渲染
,被 Reconciler 直接转成 Fiber:`
然后渲染。
这时候,组件更新了,新的 VDOM 是 A、C、E、F 后,如何处理呢?
diff 会分成
两次遍历
- 第一次 让
新的 VDOM 和 旧的 Fiber 进行对比,看有没有能复用的节点
,如果有,继续遍历,如果没有,就停止遍历。- 判断新的 VDOM 有没有遍历完,如果遍历完,把旧的 Fiber 中剩下的节点删掉即可。若新的 VDOM 没有遍历完,则进行第二次遍历。
- 第二次遍历时,
把 旧的 Fiber 中剩下的 节点,放入一个 Map
,然后遍历 新的VDOM 剩下的节点,看当前遍历的 VDOM 有没有存在于这个 Map 里面,若存在,则表明可以复用,打上更新的标记。- 遍历完 新的 VDOM 后,旧的 Fiber 剩下的节点打上删掉标记,新的 VDOM 中新增的节点打上新增标记
以上就是 React 16后,Reconciler 过程中 diff 的过程。
下面直接图解上述过程:
第一轮遍历:
线性一一对比
,当前 新VDOM 的节点A 和 旧的 Fiber 中的节点A 可以复用,打上更新标记,然后继续遍历 新VDOM 的下一节点C,发现节点C 和 Fiber 中的 节点B 不能复用,结束遍历。
此时 新VDOM 的节点还没遍历完,则进行第二次遍历
第二轮遍历:
- 接下来,把
旧的 Fiber 中,剩下的 节点 B、C、D 放入一个 Map
,key 就是节点的 key。 - 继续遍历 新VDOM 中剩下的节点,
同样去找能不能复用的节点
。比如发现只有 C 节点能在 Map 中找到,则打上更新标记。
遍历完毕后:
- Map 中剩下的
B、D 节点打上删除标记
。 - 新VDOM 中的
E、F 节点打上新增标记
。
图解如下:
知道了节点如何变化后,生成新的 Fiber 如下:
至此,我们完成了 Diff。
总结
React 16后的 diff 核心就是 查找复用节点
第一轮遍历时,线性一一对比,若 新VDOM 的当前节点 和 旧的 Fiber 当前节点不能复用,则终止遍历。
第二轮遍历时,将 旧的 Fiber 剩余节点放入 Map,继续遍历 新的VDOM 中的节点,寻找复用节点,打上更新标记
。遍历完毕后,Map中剩下的节点打上删除标记,新的VDOM 中,没找到复用的节点打上新增标记。
最后根据变化,生成新的 Fiber
,然后 Commit 阶段渲染。
以上就是 React 16 Fiber 架构下的 Diff 算法。
结尾
以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论