React提供了一个声明式地API所以你不用担忧每一次更新什么东西改变了。这使得开发应用变得简单,可是这个东西在React中如何实现的并非很明显。这篇文章会解释咱们在React的算法中所作的选择以便组件的更新是可预测的当为了高性能的app而变得速度足够快。算法
目的数组
当你使用React,在一个单独的时间点你能够考虑render()函数会建立一棵React元素的树。当下一次state或者props更新了,render()函数将会返回一个不一样的树。React随后须要解决怎样有效率地更新UI去匹配最新的树。app
对于这个最小化转变一个树到另外一个树的操做的算法问题,这里有一些通用的解决办法。然而,树中元素个数为n,最早进的算法 的时间复杂度为O(n^3) 。dom
若是咱们在React里使用这个算法,显示1000个元素将会要求按次序发生十亿次比较。这样的比较的花费过高昂了。React实现了一个探索式的O(n)算法基于两种假设:函数
实际上,这些假设几乎对全部实际使用场景都是有效的。性能
对比算法spa
当比较两棵树的时候,React首先比较两个根元素。根据根元素的type不一样比较的行为也不一样。code
不一样类型的元素component
不管什么时候根元素若是有不一样的类型,React会销毁旧的树而后从草稿中建立新的树。从<a>变成<img>,或者从<Article>变成<Comment>,或者从<Button>变成<div>,任何这样的改变都会使得整个树重建。blog
当销毁一个树的时候,旧的DOM节点就被销毁了。组件实例调用componentWillUnmount()方法。当建立了一个新的树,新的DOM节点被插入已有的DOM。组件实例依次调用componentWillMount()方法和以后的componentDidMount()方法。任何旧的树的state都会丢失。
任何根元素之下的组件也会被销毁而且state也丢失。举个例子,当比较不一样的时候:
<div> <Counter /> </div> <span> <Counter /> </span>
这样会销毁旧的Counter而后从新创建一个新的。
相同类型的DOM元素
当比较两个相同类型的React DOM元素的时候,React会观察它们的属性,保留相同的DOM节点,只更新改变了的属性。举个例子:
<div className="before" title="stuff" /> <div className="after" title="stuff" />
经过比较这两个元素,React知道只去修改className属性。
当更新style的时候,React也知道只去更新改变了的属性。举个例子:
<div style={{color: 'red', fontWeight: 'bold'}} /> <div style={{color: 'green', fontWeight: 'bold'}} />
当改变发生在这两个元素之间,React知道只去修改color样式,而不修改fontWeight。
在处理DOM节点以后,React以后会在子元素上递归计算。
相同类型的组件元素
当一个组件更新的时候,实例保持同样,以便渲染的时候state被维持。React更新底层组件实例的props去产生新元素,而且在底层实例上调用componentWillReceiveProps()和componentWillUpdate()。
接下来,render()方法被调用而且diff算法在以前的结果和新结果上递归处理。
子节点的递归
默认状况,当在DOM节点的子节点上递归时,React仅在同一时间点递归两个子节点列表,并在有不一样时产生一个变动。
举个例子,当在子元素的结尾添加一个元素的时候,两个树之间的转变:
<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>子树不变。这样的低效率会成为一个问题。
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知道了拥有2014key的元素是新元素,而拥有2015和2016的元素只是须要移动一下位置。
事实上,找到一个key一般不麻烦。你将要去显示的元素也许已经有了一个惟一的ID,所以key属性也可使用你的数据:
<li key={item.id}>{item.name}</li>
若是不是这种状况,你能够添加新的ID属性到你的模型或者哈希一些内容来生成一个key。这个key对于兄弟元素必须惟一,可是不须要全局惟一。
万不得已,你能够传递它们在数组里的索引做为一个key。若是数组不会从新排序那么这样也会起做用,可是若是从新排序程序也会变慢。
当索引用做key时,组件状态在从新排序时也会有问题。组件实例基于key进行更新和重用。若是key是索引,则item的顺序变化会改变key值。这将致使受控组件的状态可能会以意想不到的方式混淆和更新。
这里是在CodePen上使用索引做为键可能致使的问题的一个例子,这里是同一个例子的更新版本,展现了如何不使用索引做为键将解决这些reordering, sorting, 和 prepending的问题。
权衡
牢记协调算法的实现细节很是重要。React可能在每一次动做的时候会渲染整个app;最终的结果会是同样的。咱们要按期地提炼这些启发式算法为了让相同的使用场景性能更好。
在现在的实现里,你能够表示这个事实,一个子树在它的兄弟节点之中移动,可是你不能告知它移动到哪里。算法将会渲染整个子树。
由于React依赖于启发式算法,若是在它们背后的设想没有被知足,那么性能会受损。