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也丢失。举个例子,当比较不同的时候:

<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属性

为了解决这个问题,React支持一个key属性。当一个子元素拥有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知道了拥有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依赖于启发式算法,如果在它们背后的设想没有被满足,那么性能会受损。

  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. redis 列表(list)函数

    列表(list)函数 lPush 命令/方法/函数 Description Adds the string value to the head (left) of the list. Creates ...

  2. 为fastdfs文件服务器新增一个storage

    一.前言: 前期,已经搭建好了一套fastdfs文件服务器,一个tracker和一个storage,且部署在同一台服务器上,已经正式投入运行快半年了,1T的空间现在只剩下100G容量了,现在需要扩容, ...

  3. racle SQL性能优化

    (1) 选择最有效率的表名顺序(只在基于规则的优化器中有效): Oracle的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先 ...

  4. java线程学习之volatile关键字

    volatile变量的主要作用:是使变量在多个线程间可见. 在java中每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存的变量值的拷贝.当线程执行时,它在自己的工作内存区操作这些变量,为 ...

  5. mysql8操作命令(持续更新)

    mysql服务管理 查看服务状态 systemctl status mysqld.service 启动服务 systemctl start mysqld.service 关闭服务 systemctl ...

  6. knative

    office Doc Knative 简介 Install sevice example (knative) There is only one node in the cluster so we u ...

  7. 【MySQL】InnoDB 内存管理机制 --- Buffer Pool

    InnoDB Buffer Pool 是一块连续的内存,用来存储访问过的数据页面 innodb_buffer_pool_size 参数用来定义 innodb 的 buffer pool 的大小 是 M ...

  8. STM32按键输入

    下面3个接上拉电阻 WK_UP接上拉电阻 因为用到了PA,PC,PH所以要使能3个模块 STATIC静态变量只会初始化一次 每次调用flag++,不会再初始化为0:起记忆作用. 最关键的是头 件不要忘 ...

  9. JS经典面试题汉诺塔

    我爱撸码,撸码使我感到快乐!大家好我是Counter.今天给大家分享的是利用JS将汉诺塔原理实现出来,其实主要是考察一个递归的思想,复杂的问题简单化,汉诺塔应该都知道吧,具体的游戏规则,可以百度查查, ...

  10. BZOJ 5261 Rhyme

    思路 考虑一个匹配的过程,当一个节点x向后拼接一个c的时候,为了满足题目条件的限制,应该向suflink中最深的len[x]+1>=k的节点转移(保证该后缀拼上一个c之后,长度为k的子串依然属于 ...