Skip to content

Latest commit

 

History

History
58 lines (39 loc) · 3.26 KB

File metadata and controls

58 lines (39 loc) · 3.26 KB

协调

设计动力

在某一时间节点调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。 在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。 React 需要基于这两棵树之间的差别来判断如何有效率的更新 UI 以保证当前 UI 与最新的树保持同步。

这个算法问题有一些通用的解决方案,即生成将一棵树转换成另一棵树的最小操作数。 然而,即使在最前沿的算法中,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数量。

如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围。 这个开销实在是太过高昂。 于是 React 在以下两个假设的基础之上提出了一套 O(n) 的启发式算法:

  • 两个不同类型的元素会产生出不同的树;
  • 开发者可以通过 key prop 来暗示哪些子元素在不同的渲染下能保持稳定;
    在实践中,我们发现以上假设在几乎所有实用的场景下都成立。

diffing 算法

当对比两颗树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。

比对不同类型的元素

当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。举个例子,当一个元素从 变成 ,从

变成 ,或从 变成
都会触发一个完整的重建流程。

当拆卸一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 componentWillUnmount() 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。所有跟之前的树所关联的 state 也会被销毁。

比对同一类型的元素

当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。

比如:

<div className="before" title="stuff" />

<div className="after" title="stuff" />

通过比对这两个元素,React 知道只需要修改 DOM 元素上的 className 属性。 当更新 style 属性时,React 仅更新有所更变的属性。比如:

<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

通过比对这两个元素,React 知道只需要修改 DOM 元素上的 color 样式,无需修改 fontWeight。

在处理完当前节点之后,React 继续对子节点进行递归。

比对同类型的组件元素

当一个组件更新时,组件实例保持不变,这样 state 在跨越不同的渲染时保持一致。React 将更新该组件实例的 props 以跟最新的元素保持一致,并且调用该实例的 componentWillReceiveProps() 和 componentWillUpdate() 方法。

下一步,调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归。

对子节点进行递归

在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。

在子元素列表末尾新增元素时,更变开销比较小。比如: