这个算法用来比较虚拟dom和真实dom,从而最小化真实dom的更新
本质上是对两颗Fiber树的对比
(在Vue中是对旧VDOM树与新VDOM树的对比)
在不剪枝的情况下,时间复杂度接近O(n^3)
基于最长公共子序列 (LCS) 的朴素计算为O(n2),加上需要递归比较子节点,时间复杂度来到O(n3)
diff的限制:
- 只进行同层级比较
- 新旧节点的type不同直接删除旧节点,创建新节点
- 通过 key 来复用节点
三个层级的比较策略
- 第一步剪枝,Tree层级的比较
每次递归开始的第一步,比较两棵树的根节点,不一样直接销毁整棵树重建 - Component层级
type同 => 保持组件实例不变只更新props,然后进入组件的render结果进行Element层级的比较
type不同 => 销毁重建 - Element层级
type同 => 更新属性
type不同 => 销毁重建
diff算法
根据fiber节点的child类型判断是单节点diff还是多节点diff,仅当child类型为数组时进行多节点diff
1. 单节点diff
key同 且 type不同 => 执行 deleteRemainingChildren 将 child 及其兄弟 fiber 都标记删除。
key不同 => 将child标记删除,然后遍历兄弟fiber
在 React 中,一个 Fiber 节点(即组件实例)的状态(State)和 Hooks(如 useState, useEffect)是严格绑定在它的 type 上的。让一个不再渲染的旧组件 Fiber 保持 Hooks 激活状态是错误的,会导致内存泄漏和不可预测的行为,必须立即销毁重建。
2.多节点diff
两轮遍历
1. 第一轮
key同 type同可复用
key 不同导致不可复用,立即跳出整个遍历,第一轮遍历结束。
key 相同 而type 不同导致不可复用,会将 oldFiber 标记为 DELETION,并继续遍历
newChildren和oldFiber其一遍历完成就跳出
2. 第二轮
-
newChildren 与 oldFiber 同时遍历完
只需在第一轮遍历进行组件更新。此时 Diff 结束。 -
newChildren 没遍历完,oldFiber 遍历完
遍历剩下的 newChildren 为生成的 fiber 依次标记 Placement。 -
newChildren 遍历完,oldFiber 没遍历完
遍历剩下的 oldFiber,依次标记 Deletion。 -
newChildren 与 oldFiber 都没遍历完
处理移动的节点
react会把剩余的旧 Fiber 及其在旧列表中的索引存入一个 Map 中
每个新节点有两个index,oldIndex 和 maxIndex,
oldIndex:新节点在 Map 中查找对应的旧 Fiber,得到它在旧列表中的原始索引。
maxIndex: 记录当前遇到的所有旧节点中的最大原始索引。
当 oldIndex>maxIndex 时,将 oldIndex 的值赋值给 maxIndex
当 oldIndex=maxIndex 时,不操作
当 oldIndex<maxIndex 时,将当前节点移动到 index 的位置
