“调和”和 Diff

“调和”又译为“协调”

Virtual DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的”DOM同步,这一过程叫作协调(调和)。

所以调和过程并不能和 Diff 划等号

  • 调和是“使一致”的过程
  • Diff 是“找不同”的过程

React 的源码划分了

  • Core
  • Renderer
  • Reconciler
    • 调和器的源码位于 src/renderers/shared/stack/reconciler
    • 调和器所做的工作包括组件的挂载、卸载、更新等过程

Diff 是调和过程中最具代表性的一环

根据 Diff 实现形式的不同,调和过程划分为

  • React15 代表的栈调和
  • React16 代表的 Fiber 调和

Diff 策略的设计思想

要想找出两个树结构之间的不同:

传统的计算方法是通过循环递归进行树节点为的一一对比

  • 算法的时间复杂度是 React15 中的“栈调和(Stack Reconciler)” - 图1
  • 对浏览器来说,仍然意味着一场性能灾难
    image.png
  • OJ 中相对理想的时间复杂度是 React15 中的“栈调和(Stack Reconciler)” - 图3React15 中的“栈调和(Stack Reconciler)” - 图4,当复杂度攀升至 React15 中的“栈调和(Stack Reconciler)” - 图5 就要寻找性能优化手段

React 团队把复杂度降低设立两个大前提

  • 若两个组件属于同一个类型,它们将拥有相同的 DOM 树形结构
  • 处于同一层级的一组子节点,可通过设置 Key 作为唯一标识,从而维持各个节点在不同渲染过程中的稳定性

还有一个与实践紧密的规律

  • DOM 节点之间跨层级操作不多,同层级操作是主流

    Diff 逻辑的拆分与解读

    三个“要点”
  1. Diff 算法性能突破的关键点在于“分层对比”
    • DOM 节点之间跨层级操作不多,同层级操作是主流
    • Diff 过程只先对相同层级的节点对比
      image.png
    • 对于跨层级的操作,只不能分辨移动的行为。
      只能移除出子树节进行销毁,移入子树作新增。销毁加上重建的代价
      image.png
  2. 类型一致的节点才有继续 Diff 的必要性
  • 若两个组件属于同一个类型,它们将拥有相同的 DOM 树形结构
  • 减少递归“一刀切”策略:类型的一致性关键字来递归的必要性
    • React 认为,只有同类型的组件,才有进一步对比的必要性
      image.png
  1. key 属性的设置,可以尽可能重用同一层级内的节点
  • 处于同一层级的一组子节点,可通过设置 Key 作为唯一标识,从而维持各个节点在不同渲染过程中的稳定性
  • 重用节点的好帮手:key 属性帮 React “记住”节点

    key 是用来帮助 React 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,React 则会触发 UI 的重渲染。这是一个非常有用的特性。

    • 解决了同一层级下节点的重用问题:
      image.png
      • 原本新增 1 个节点就能搞定的事情,现在却又是删除又是重建
      • 插入节点的形式是高频操作,key 属性来帮助重用节点
        image.png