React提供了一个声明式地API因此你不用担心每一次更新什么东西改变了。这使得开发应用变得简单,但是这个东西在React中如何实现的并不是很明显。这篇文章会解释我们在React的算法中所做的选择以便组件的更新是可预测的当为了高性能的app而变得速度足够快。

目的

当你使用React,在一个单独的时间点你可以考虑render()函数会创建一棵React元素的树。当下一次state或者props更新了,render()函数将会返回一个不同的树。React随后需要解决怎样有效率地更新UI去匹配最新的树。

对于这个最小化转变一个树到另一个树的操作的算法问题,这里有一些通用的解决办法。然而,树中元素个数为n,最先进的算法 的时间复杂度为O(n^3) 。

如果我们在React里使用这个算法,显示1000个元素将会要求按次序发生十亿次比较。这样的比较的花费太高昂了。React实现了一个探索式的O(n)算法基于两种假设:

  1. 两个元素类型不同会造成不同的树
  2. 借助一个key属性,开发者可以暗示哪一个子元素在不同的渲染的时候会稳定不变

实际上,这些假设几乎对所有实际使用场景都是有效的。

对比算法

当比较两棵树的时候,React首先比较两个根元素。根据根元素的type不同比较的行为也不同。

不同类型的元素

无论何时根元素如果有不同的类型,React会销毁旧的树然后从草稿中创建新的树。从<a>变成<img>,或者从<Article>变成<Comment>,或者从<Button>变成<div>,任何这样的改变都会使得整个树重建。

当销毁一个树的时候,旧的DOM节点就被销毁了。组件实例调用componentWillUnmount()方法。当创建了一个新的树,新的DOM节点被插入已有的DOM。组件实例依次调用componentWillMount()方法和之后的componentDidMount()方法。任何旧的树的state都会丢失。

任何根元素之下的组件也会被销毁并且state也丢失。举个例子,当比较不同的时候:

  1. <div>
  2. <Counter />
  3. </div>
  4.  
  5. <span>
  6. <Counter />
  7. </span>

这样会销毁旧的Counter然后重新建立一个新的。

相同类型的DOM元素

当比较两个相同类型的React DOM元素的时候,React会观察它们的属性,保留相同的DOM节点,只更新改变了的属性。举个例子:

  1. <div className="before" title="stuff" />
  2.  
  3. <div className="after" title="stuff" />

通过比较这两个元素,React知道只去修改className属性。

当更新style的时候,React也知道只去更新改变了的属性。举个例子:

  1. <div style={{color: 'red', fontWeight: 'bold'}} />
  2.  
  3. <div style={{color: 'green', fontWeight: 'bold'}} />

当改变发生在这两个元素之间,React知道只去修改color样式,而不修改fontWeight。

在处理DOM节点之后,React之后会在子元素上递归计算。

相同类型的组件元素

当一个组件更新的时候,实例保持一样,以便渲染的时候state被维持。React更新底层组件实例的props去产生新元素,并且在底层实例上调用componentWillReceiveProps()和componentWillUpdate()。

接下来,render()方法被调用并且diff算法在之前的结果和新结果上递归处理。

子节点的递归

默认情况,当在DOM节点的子节点上递归时,React仅在同一时间点递归两个子节点列表,并在有不同时产生一个变更。

举个例子,当在子元素的结尾添加一个元素的时候,两个树之间的转变:

  1. <ul>
  2. <li>first</li>
  3. <li>second</li>
  4. </ul>
  5.  
  6. <ul>
  7. <li>first</li>
  8. <li>second</li>
  9. <li>third</li>
  10. </ul>

React将会匹配两个<li>first</li>树,匹配两个<li>second</li>树,然后再插入<li>third</li>树。

如果你很简单地实现它,在起始位置插入一个元素那样性能就会很差。举个例子,下面两个树的转变就不好:

  1. <ul>
  2. <li>Duke</li>
  3. <li>Villanova</li>
  4. </ul>
  5.  
  6. <ul>
  7. <li>Connecticut</li>
  8. <li>Duke</li>
  9. <li>Villanova</li>
  10. </ul>

React会使每一个子元素变化而不是保持<li>Duke</li>和<li>Villanova</li>子树不变。这样的低效率会成为一个问题。

key属性

为了解决这个问题,React支持一个key属性。当一个子元素拥有key属性,React使用key来匹配原始的树和之后的新树。举个例子,添加一个key到我们上面那个效率低的例子里就可以让转换变得有效率:
  1. <ul>
  2. <li key="2015">Duke</li>
  3. <li key="2016">Villanova</li>
  4. </ul>
  5.  
  6. <ul>
  7. <li key="2014">Connecticut</li>
  8. <li key="2015">Duke</li>
  9. <li key="2016">Villanova</li>
  10. </ul>

现在React知道了拥有2014key的元素是新元素,而拥有2015和2016的元素只是需要移动一下位置。

事实上,找到一个key通常不麻烦。你将要去显示的元素也许已经有了一个唯一的ID,因此key属性也可以使用你的数据:

  1. <li key={item.id}>{item.name}</li>

如果不是这种情况,你可以添加新的ID属性到你的模型或者哈希一些内容来生成一个key。这个key对于兄弟元素必须唯一,但是不需要全局唯一。

万不得已,你可以传递它们在数组里的索引作为一个key。如果数组不会重新排序那么这样也会起作用,但是如果重新排序程序也会变慢。

当索引用作key时,组件状态在重新排序时也会有问题。组件实例基于key进行更新和重用。如果key是索引,则item的顺序变化会改变key值。这将导致受控组件的状态可能会以意想不到的方式混淆和更新。

这里是在CodePen上使用索引作为键可能导致的问题的一个例子,这里是同一个例子的更新版本,展示了如何不使用索引作为键将解决这些reordering, sorting, 和 prepending的问题。

权衡

牢记协调算法的实现细节非常重要。React可能在每一次动作的时候会渲染整个app;最终的结果会是一样的。我们要定期地提炼这些启发式算法为了让相同的使用场景性能更好。

在如今的实现里,你可以表示这个事实,一个子树在它的兄弟节点之中移动,但是你不能告知它移动到哪里。算法将会渲染整个子树。

因为React依赖于启发式算法,如果在它们背后的设想没有被满足,那么性能会受损。

  1. 这个算法不会去试图匹配组件类型不同的子树。如果你看到交替的两种组件类型有着相似的输出,你也许想要使它们的类型一样。事实上,我们还没有发现这样会出现问题。
  2. key属性必须稳定,可预测还有唯一。不稳定的key(就像那些通过Math.random()生成的随机数)将会造成许多组件实例和DOM节点不必要的重复创建,这样就会使性能变差并且在子组件中丢失state。

React文档(二十一)协调的更多相关文章

  1. React文档(十一)提升state

    经常有些组件需要映射同一个改变的数据.我们建议将共用的state提升至最近的同一个祖先元素.我们来看看这是怎样运作的. 在这一节中,我们会创建一个温度计算器来计算提供的水温是否足够沸腾. 我们先创建一 ...

  2. React文档(十三)思考React

    在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...

  3. React文档(二十四)高阶组件

    高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...

  4. react文档demo实现输入展示搜索结果列表

    文档页面地址:https://doc.react-china.org/docs/thinking-in-react.html 该文档只给了具体实现思路,下面是我实现的代码. 初学react,如果有写的 ...

  5. React文档(一)安装

    React是一个灵活的可以用于各种不同项目的框架,你可以用它来写新应用,你也可以逐步将它引进已有的代码库而不用重写整个项目. 试用React 如果你想玩一玩React,那么就去CodePen上试一试. ...

  6. ZooKeeper文档(二)

    ZooKeeper:因为协调的分布式系统是一个动物园 ZooKeeper对分布式应用来说是一个高性能的协调服务.它暴露通常的服务-比如命名,配置管理,同步,和组服务-用一种简单的接口,所以你不用从头开 ...

  7. 基于Zabbix API文档二次开发与java接口封装

    (继续贴一篇之前工作期间写的经验案例) 一.           案例背景 我负责开发过一个平台的监控报警模块,基于zabbix实现,需要对zabbix进行二次开发. Zabbix官方提供了Rest ...

  8. MongoDB文档(二)--查询

    (一)查询文档 查询文档可以使用以下方法 # 以非结构化的方式显示所有的文档 db.<collectionName>.find(document) # 以结构化的方式显示所有文档 db.& ...

  9. 翻译qmake文档(二) Getting Started

    翻译qmake文档 目录 原英文文档: http://qt-project.org/doc/qt-5/qmake-tutorial.html         本教程教讲授qmake基础知识.这个手册里 ...

  10. 通过VuePress管理项目文档(二)

    通过vue组件实现跟:Element相似的效果.需要在VuePress网站中将自己的项目中的Vue组件运行结果展示在页面中. 至于如何将组件在VuePress网站中展示请参考:https://segm ...

随机推荐

  1. win10配置环境变量

  2. spring-boot集成activiti的model遇到问题汇总

    按照网上的七拼八凑整合网页版的部署将遇到的问题归置如下: 本人的springboot版本是:1.5.13.RELEASE 工作流相关: <!--工作流--> <dependency& ...

  3. java集合类学习笔记1

    一.集合的接口 java集合类库也将接口与实现相分离.首先看一下大家都熟悉的数据结构-队列是如何分离的.队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数.当需要 ...

  4. 自娱自乐RN版小说APP历程记录

    当前rn版本 "react": "16.6.3" "react-native": "0.58.5" 通过react-na ...

  5. Java锁与CAS

    一.加锁与无锁CAS 在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观派而已,他们总会认为发 ...

  6. CEF JS实现获取剪贴板图片的DataURL

    转载:https://www.deanhan.cn/js-paste-upload.html 转载:https://segmentfault.com/a/1190000002915597 转载:htt ...

  7. Hive性能优化上的一些总结

    https://blog.csdn.net/mrlevo520/article/details/76339075 1.介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据 ...

  8. 使用telnet进行Dubbo接口测试

    telnet进入dubbo 查看pid $ jps -l 26048 org.apache.catalina.startup.Bootstrap 12388 org.jetbrains.jps.cmd ...

  9. <抽象工厂>比<工厂方法>多了啥

    前言:仅当复习讨论,写得不好,多多指教! 上一篇文章<比多了啥>介绍了简单工厂模式和工厂方法模式.本篇文章则讲最后一个工厂----抽象工厂.如果对工厂方法比较模糊的,可以返回上一篇文章复习 ...

  10. Cent OS & Windows 双系统自定义引导菜单

    系统环境 系统:Windows 10 (64-bit) & Cent OS 7 (64-bit) 引导程序:Grub2 编辑 grub.cfg 为了方便,在终端使用命令 su 输入密码进入超级 ...