React 栈调和机制下 Diff 算法

627 阅读3分钟

虚拟Dom是现代前端框架趋同的一个特质,

虚拟 DOM 解决的关键问题有以下两个:

  1. 研发体验/研发效率的问题:虚拟DOM的出现,为数据驱动视图这一思想提供了高度可用的载体,使得前端开发能够基于函数式 UI 的编程方式实现高效的声明式编程
  2. 跨平台的问题:虚拟 DOM 是对真实渲染内容的一层抽象。若没有这一层抽象,那么视图层将和渲染平台紧密耦合在一起,为了描述同样的视图内容,你可能要分别在 Web 端和 Native 端写完全不同的两套甚至多套代码。但现在中间多了一层描述性的虚拟 DOM,它描述的东西可以是真实 DOM,也可以是iOS 界面、安卓界面、小程序......同一套虚拟 DOM,可以对接不同平台的渲染逻辑,从而实现“一次编码,多端运行”

在组件更新的过程中,通过如 ReactDOM 等类库使虚拟 DOM 与“真实的” DOM 同步,这一过程叫作协调(调和)。

Diff 算法其实就是“找不同”的过程。在计算机科学领域,要想找出两个树结构之间的不同, 传统的计算方法是通过循环递归进行树节点的一一对比, 这个过程的算法复杂度是 O (n3) ,React基于一定的设定和处理,将将 O (n3) 复杂度转换成 O (n) ,这里首先有两个大前提:

  1. 若两个组件属于同一个类型,那么它们将拥有相同的 DOM 树形结构;
  2. 处于同一层级的一组子节点,可用通过设置key作为唯一标识,从而维持各个节点在不同渲染过程中的稳定性
  3. DOM 节点之间的跨层级操作并不多,同层级操作是主流。

接下来我们就一起看看 React 是如何巧用这 3 个规律,打造高性能 Diff 的。

  1. Diff 算法性能突破的关键点在于“分层对比”

    结合“DOM 节点之间的跨层级操作并不多,同层级操作是主流”这一规律,React 的 Diff 过程直接放弃了跨层级的节点比较,它只针对相同层级的节点作对比,如下图所示。这样一来,只需要从上到下的一次遍历,就可以完成对整棵树的对比,这是降低复杂度量级方面的一个最重要的设计。

    需要注意的是:虽然栈调和将传统的树对比算法优化为了分层对比,但整个算法仍然是以递归的形式运转的,分层递归也是递归==(先序深度优先遍历 ,对比方式用的分层对比)==

  2. 类型一致的节点才有继续 Diff 的必要性;

  3. key 属性的设置,可以帮我们尽可能重用同一层级内的节点