React提供了一个声明式的API,因此你没必要担忧每次DOM更新时内部会修改哪些东西。虽然在React中并非那么明显地告诉你具体如何实现的,不过这也让编写应用变得更加容易。javascript
本文会详细解释在React中的“diffing”
算法是怎么作的,以便组件更新是可预测的,从而让高性能应用变得足够快。java
当使用React时,在单个时间点,您能够将render()
函数看作是在建立React元素树。 在下一个state
或props
更新时render()
函数将返回一个不一样的React元素树。 React须要弄清楚如何高效地更新UI去匹配上最新的元素树。算法
对于将一个树变换成另外一个树的最小操做数的算法问题,如今已经存在一些比较通用的解决方案。 然而,那些现有的最早进的技术算法都有O(n^3)
的复杂度(n是树中的元素的数量)。数组
若是在React中使用这些算法,显示1000个元素将须要大约十亿次比较。 这个真的代价太昂贵了。 相反,React实现了一个基于两个假设
直观推断出的O(n)
算法:dom
不一样类型的两个元素将产生不一样的树。函数
开发人员能够在不一样渲染之间使用key
属性来表示哪些子元素是稳定的。性能
实际上,这两条假设对几乎全部的实际使用都是有效的。spa
当比较两棵DOM树的差别时,React首先比较两个根元素。 若是根元素的类型不一样,那么行为也是不一样的。code
每当根元素是不一样的类型时,React将删除旧的DOM树并从头开始从新构建新的DOM树。 从<a>
到<img>
、从<Article>
到<Comment>
、从<Button>
到<div>
,只要不同就会彻底从新构建。component
当删除就的DOM树时,旧的DOM节点也被删掉。 这个时候组件实例触发componentWillUnmount()
函数 。当构建一个新的DOM树时,新的DOM节点会被插入到DOM中。 组件实例触发componentWillMoun()
和componentDidMount()
。 与以前旧的DOM树相关联的任何state
也都将丢失。
在根元素之下的任何组件将被卸载而且它们的state
也会所有丢失。 例如:
// 从 <div> <Counter /> </div> // 变为 <span> <Counter /> </span>
由于根元素从div
变为了span
,因此旧的Counter
组件将被销毁,而后再从新构建一个新的。
当比较相同类型的两个React DOM元素时,React会先查看二者的属性差别,而后保留相同的底层DOM节点,仅仅去更新那些被更改的属性。 例如:
<div className="before" title="hello" /> <div className="after" title="hello" />
经过比较这两个元素属性,React就会知道只须要修改底层DOM节点上的className
便可。
当更新style
属性时,React也会知道只须要更新style
中的那些已更改的属性。 例如:
<div style={{color: 'red', width: '300px'}} /> <div style={{color: 'red', width: '400px'}} />
当在这两个元素之间转换时,React知道只需修改width
,而不是color
。
处理根DOM节点后,React会根据上面的判断逻辑对子节点进行递归扫描。
当组件更新时,实例保持不变,所以在不一样的渲染之间组件内的state
是保持不变的。 React会更新底层组件实例的props
来匹配新元素,并在底层实例上调用componentWillReceiveProps()
和componentWillUpdate()
。
接下来,调用render()
方法,diff
算法就会对上一个结果和新结果进行递归比较。
默认状况下,当对DOM节点的子元素进行递归时,React只是同时迭代两个子元素lists,并在有差别时产生变化。
例如,当在子元素的末尾再添加一个元素时,这两个树之间就会有一个的很好转换效果:
<ul> <l1>one</li> <li>two</li> </ul> <ul> <li>one</li> <li>two</li> <li>three</li> </ul>
React将匹配两个<li>one</li>
树,匹配两个<li>two</li>
树,而后插入一个<li>three</li>
树。
可是,不要太天真了。若是在子元素的开头部分插入一个元素的话,性能会便的不好。 例如,这两棵树之间的转换效果就不好:
<ul> <li>one</li> <li>two</li> <ul> <ul> <li>zero</li> <li>one</li> <li>two</li> <ul>
这种状况React将更改每一个子元素 ,而不会意识到它能够保持<li>one</li>
和<li>two</li>
子元素树无缺无损。 这种低效率的状况是一个必须注意的问题。
为了解决上面的问题,React提供了一个key
属性。 当子元素有key
属性时,React使用key
将原始树中的子元素与后续树中的子元素进行匹配。 例如,上面的那个低效例子添加一个key
就可让子元素树转换变的颇有效:
<ul> <li key="1">one</li> <li key="2">two</li> <ul> <ul> <li key="0">zero</li> <li key="1">one</li> <li key="2">two</li> <ul>
如今React就能够知道key="0"
的元素是新的,而且key="1"
和key="2"
的元素只需移动便可。
在实践中,使用一个惟一的key
并不难。 您要显示的元素可能已具备惟一的ID
,所以key
能够来自你本身的数据中:
<li key={item.id>{item.name}</li>
若是不是这样,你能够向数据模型中给每一项数据添加一个新的ID
属性,或者对内容的某些部分进行哈希生成key
。 key
属性只有在其兄弟元素之间是惟一的,并非全局惟一的。
最后一种方式是能够将数组中的索引做为key
。 若是数组中的每一项不须要从新排序,一样也能够很好地工做,可是万一须要从新排序的话,这会变的很慢。
要记住重要的是,diffing
算法是一个具体的实现细节。 React能够在每一个操做上去从新渲染应用; 最终结果都是同样的。
在当前的实现中,你能够看到一个事实是一个子树已经成功移动到它的兄弟元素当中,但你不能告诉它已经移动到别的地方。 该算法将从新渲染这个完整的子元素树。
由于React很依赖这个直观推断的算法来判断DOM是否须要从新处理,若是不能知足这个算法的那两个假设条件前提,应用的性能将会受到很大影响。
该算法不会去尝试匹配那些不一样组件类型的子元素树。 若是你看到本身在返回类似输出结果的两个组件类型之间来来回回,你可能须要把它们改成相同的类型组件。
key
属性应该是稳定,可预测和惟一的。 不稳定的键(如使用Math.random()
生的key
)将致使许多组件实例和DOM节点进行没必要要地重复建立,这可能致使子组件中的性能下降和state
丢失。