Evan Zou

Welcome to my blog.

Javascript follower.


Hi, I am Evan, nice to meet you all.

React Reconciliation(调和)

动机

当使用 React 时,在下一个 steate 或 props 更新时,render()函数将会返回一个不同的 React 树,接下来 React 会找出最高效地方式更新 UI。 目前存在大量通用的方法能够以最少的操作步骤将一个树转化成另外一棵树。然而,这个算法是复杂度为 O(n3),其中 n 为树中元素的个数。即如果有 1000 个元素,那么每次更新都要进行 10 亿次比较。 然而,React 给予一下两个假设实现了复杂度为 O(n)的算法,即每次更新只需要进行 1000 次的比较:

  1. 不同类型的两个元素将会产生不同的树。
  2. 开发人员可以使用一个 key prop 来指示在不同的渲染中那个那些元素可以保持稳定。

一. Diffing 算法

当比较不同的两个树的时候,React 首先比较两个根元素。根据很元素的类型,他有不同的行为。

1.1 元素类型不相同

无论什么时候,当根元素类型不同时,React 将会销毁原先的树并重新构建新的树。 当销毁原先的树时,之前的 DOM 节点将全部被销毁。组件会执行componentWillUnmount()。当构建新的树时,新 DOM 节点将会插入 DOM 中。组件将会执行componentWillMount() 以及 componentDidMount() 。与之前旧的树相关的 state 都会丢失。

1.2 DOM 元素类型相同

当比较两个相同类型的 React DOM 元素时,React 检查它们的属性(attributes),保留相同的底层 DOM 节点,只更新反生改变的属性(attributes)。 当处理完当前 DOM 节点后,React 会递归处理子节点。

1.3 相同类型的组件

当一个组件更新的时候,组件实例保持不变,以便在渲染中保持 state。React 会更新组件实例的属性来匹配新的元素,并在元素实例上调用 componentWillReceiveProps()componentWillUpdate()

1.4 子元素递归

例如,当给子元素末尾添加一个元素,在两棵树之间转化中性能就不错:

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React 会比较两个 <li>first</li> 树与两个 <li>second</li> 树,然后插入 <li>third</li> 树。

如果在开始处插入一个节点也是这样简单地实现,那么性能将会很差。例如,在下面两棵树的转化中性能就不佳。

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React 将会改变每一个子节点而没有意识到需要保留 <li>Duke</li><li>Villanova</li> 两个子树。这种低效是一个问题。

1.5 Keys

React 提供了一个 key 属性(attributes)。当节点有了 key,React 使用 key 去比较原来的书的子节点和之后的节点。当我们添加一个 key 时,可以是上面抵消的例子转换变高效:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

此时 React 知道只有key="2014"的元素是新的,剩下的两个元素仅仅只是被移动而已。这样就会保留剩下的两个元素只更新新加入的元素。

1.6 总结

因为 React 依赖这两个启发式,如果背后的假设没有得到满足,性能将会受到影响。

  1. 算法不会尝试匹配不同节点类型的子树。
  2. keys 应该是稳定的,可预测的并且是唯一的。不稳定的 key (类似于 Math.random() 函数的结果)可能会产生非常多的组件实例并且 DOM 节点也会非必要性的重新创建。这将会造成极大的性能损失和组件内 state 的丢失
最近的文章

React Lifecycle(生命周期)

按照我的理解,生命周期主要分为一下几个步骤: componentWillMount() 在组件第一次渲染之前调用,这个是在 render 方法调用前可修改 state 的最后一次机会。这个方法基本不会用到。 render() 渲染虚拟 dom。 componentDidMount() 在组件第一次渲染后立即调用。一般会在这个方法里执行 ajax,setinterval,settimeout 等方法。因为 ajax 是异步请求,如果在 componentW...…

React继续阅读
更早的文章

CSS z-index失效问题

前言z-index在CSS里一直是比较神奇的一种属性,在项目过程中,总是会出现时灵时不灵的状况,以下是我总结的几类失效原因,希望与大家一起分享。原因 父标签 position属性为relative。 问题标签无position属性(不包括static)。 问题标签含有浮动(float)属性。 …

CSS继续阅读