You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
既然 DOM 节点跨层级的移动,可以少到忽略不计,针对这种现象,React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只对相同层级的DOM节点进行比较,即同一父节点下的所有子节点,当发现该节点已经不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。
深入diff 算法
diff 作为 Virtual DOM 的加速器,其算法上的改进优化是React页面渲染的基础和性能保障,本节从源码入手,深入剖析diff算法。
React 中醉值得称道的莫过于Virtual DOM与diff的完美结合,尤其是其高效的diff算法,可以帮助我们在页面蔌渲染的时候,计算出Virtual DOM真正变化的部分,并只针对该部分进行的原生DOM操作,而不是渲染整个页面,从而保证了每次操作后,页面的高效渲染。
一. 传统的 diff 算法
计算一个树形结构转换成另一个树形结构的最少操作,是一个复杂且值得研究的问题,传统 diff 算法通过循环递归的方法对节点进行操作,算法复杂度 为O(n^3),其中n为树中节点的总数,这效率太低了,如果 React 只是单纯的引入 diff 算法,而没有任何的优化的话,其效率远远无法满足前端渲染所需要的性能。那么React 是如何实现一个高效、稳定的 diff 算法。
二. diff 源码解读
React 将 Virtual DOM 树转换为 actual DOM 树的最小操作的过程称为调和, diff 算法便是调和的结果,React 通过制定大胆的策略,将 O(n^3)的时间复杂度转换成 O(n)。
1. diff 策略
下面是 React diff 算法的 3 个策略:
基于以上三个策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化。
2. tree diff
对于策略一,React 对树的算法进行了简单明了的优化,即对树进行分层比较,两颗树只会对同一层级的节点进行比较。
既然 DOM 节点跨层级的移动,可以少到忽略不计,针对这种现象,React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只对相同层级的DOM节点进行比较,即同一父节点下的所有子节点,当发现该节点已经不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。
那么就会有这样的问题:
如果出现了 DOM 节点跨层级的移动操作,diff 会有怎样的表现喃?
我们举个例子看一下:
如下图2-1,A节点(包括其子节点)整个需要跨层级移动到D节点下,React会如何操作喃?
图2-1 DOM层级变换
由于 React 只会简单的考虑同层级节点的位置变换,对于不同层级的节点,只有创建和删除操作。当根节点R发现子节点中A消失了,就会直接销毁A;当D节点发现多了一个子节点A,就会创建新的A子节点(包括其子节点)。执行的操作为:
create A —> create B —> create C —> delete A
所以。当出现节点跨级移动时,并不会像想象中的那样执行移动操作,而是以 A 为根节点的整个树被整个重新创建,这是影响 React 性能的操作,所以 官方建议不要进行 DOM 节点跨层级的操作。
3. component diff
React 是基于组件构建应用的,对于组件间的比较所采取的策略也是非常简洁、高效的。
shouldComponentUpdate()
来判断该组件是否需要大量 diff 算法分析。图3-1 component diff
如上图3-1,当 D 组件变成 G 时,即使这两个组件结构相似,但一旦 React 判断D和G是两个不同类型的组件时,就不会再比较这两个组件的结构,直接进行删除组件D, 重新创建组件G及其子组件。虽然这两个组件是不同类型单结构类似,diff 算法会影响性能,正如 React 官方博客所言:
不同类型的组件很少存在相似 DOM 树的情况,因此,这种极端因素很难在实际开发过程中造成重大影响。
4. element diff
当节点处于同一层级时,diff 提供三种节点操作:
下面由三个例子加深我们的理解
React发现这样操作非常繁琐冗余,因为这些集合里含有相同的节点,只是节点位置发生了变化而已,却发生了繁琐的删除、创建操作,实际上只需要对这些节点进行简单的位置移动即可。
针对这一现象,React 提出了优化策略:
允许开发者对同一层级的同组子节点,添加唯一key进行区分,虽然只是小小的改动,但性能上却发生了翻天覆地的变化。
进行对新旧集合的 diff 差异化对比,通过 key 发现新旧集合中包含的节点是一样的,所以可以通过简单的位置移动就可以更新为新集合,React 给出的 diff 结果为:B、D不做任何操作,A、C移动即可。
图4-2 对节点进行 diff 差异化对比
步骤:
初始化,lastIndex = 0, nextIndex = 0
从新集合取出节点B,发现旧集合中也有节点B,并且B.__mountIndex = 1,lastIndex = 0,不满足 B._mountIndex < lastIndex,则不对B操作,并且更新 lastIndex= Math.max(prevChild._mountIndex, lastIndex),并将B的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,即B._mountIndex = 0, nextIndex ++ 进入下一步
从新集合取出节点A,发现旧集合中也有节点A,并且A.__mountIndex = 0,lastIndex = 1,满足 A._mountIndex < lastIndex,则对A进行移动操作,enqueue( updates, makeMove(prevChild, lastPlacedNode, nextIndex))并且更新 lastIndex= Math.max(prevChild._mountIndex, lastIndex),并将A的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,即A._mountIndex = 1, nextIndex ++ 进入下一步
依次进行操作,可以根据下面代码执行的步骤实现,这里不再赘述
操作为:
图4-3 创建、移动、删除节点
可以看出在这个例子中,有新增的节点,还有需要删除的节点,具体怎么操作,请大胆的尝试一下吧
5. 源码
本节是对《深入React技术栈》中关于 diff 算法的理解和总结
The text was updated successfully, but these errors were encountered: