React进阶笔记8(协调Reconciliation)

协调(Reconciliation)

React提供了一组声明式的API,让你不必关心每次更新的变化,这样使得应用的编写容易了很多。
但是在React中如何实现还并不太清晰,这篇文章解释了React对比算法的选择,让组件更新可预测并且使得高性能足够的快。

目的

当你使用React,在单一的时间点,你可以考虑render()函数作为创建React函数的树,React需要算出如何更新UI来匹配最新的树(dom)

有一个解决方案是:

将一棵树转换为另一棵树的最小操作数算法问题的通用方案。然而树种元素的个数为n,最先进的算法 的时间复杂度为O(n3) 。

如果我们在React中使用,展示1000个元素,则需要10亿次的比较,这样的操作台昂贵。相反,React基于这两点的假设,实现了一个启发的O(n)算法:

①两个不同类型的元素将产生两颗不同的树
②通过渲染器附带的key属性,开发者可以示意,那些子元素是稳定的。

实践中,这种假设适用于大部分的应用场景的。

对比算法

当对比两棵树时,React首先比较他们的根节点。根节点的type不同,他们的行为也不同。

不同类型的元素

每当根元素有不同的类型,React就会卸载旧树,创建新树,从<a><img>或从<Article><Comment>,或从<Button><div>,任何的调整都会导致全部重建。

当树被卸载,旧的DOM节点将被销毁。组件实例会调用componentWillUnmount()。当构建一棵新树,新的DOM节点被插入到DOM中。组件实例将依次调用componentWillMount()componentDidMount()。任何与旧树有关的状态都将丢弃。

这个根节点下,所有的组件都会被卸载,同时他们的状态会被销毁。
以下的节点对比前后:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

由于根节点换了,所以组件<Counter>将会重载新的组件。

相同类型的DOM元素

当比较2个相同的React DOM元素时,React则会观察两者的属性。

当比较两个相同类型的React DOM元素时,React则会观察二者的属性,保持相同的底层DOM节点,并仅更新变化的属性。例如:

<div className="before" title="stuff" />
<div className="after" title="stuff" />

通过比较两个元素,React知道仅更改底层DOM元素的className

当更新style时,React同样知道仅更新变更的属性。例如:

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

当在调整两个元素时,React知道仅改变color样式而不是fontWeight。

在处理完DOM元素后,React递归其子元素。

相同类型的组件元素

当组建更新时,实例还是保持一致。这样能让状态在渲染之间保留。React通过更新底层组件的props来渲染新的元素,并且在底层的组件上,依次调用componentWillRevicePropscomponentWillUpdate的方法。

接下来render()方法被调用,同时对比算法 递归处理之前的结果和新的结果。

递归子节点

默认情况下,当递归DOM节点的子节点,React只在同一个时间点,递归2个子节点列表。并且在有发生不同的时候,产生一个变更。

如,当在子节点末尾增加一个元素,两棵树的转换效果很好:

<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>子树。低效成了一个问题。

keys

为了解决以上这个低效的问题,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知道带有'2014'的key的元素是新的,并仅移动带有'2015'和'2016'的key的元素。

实践中,发现key通常不难。你将展示的元素可能已经带有一个唯一的ID,因此key可以来自于你的数据中:

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

当这已不再是问题,你可以给你的数据增加一个新的ID属性,或根据数据的某些内容创建一个哈希值来作为key。

key必须在其兄弟节点中是唯一的,而非全局唯一。

万不得已,你可以传递他们在数组中的索引作为key。若元素没有重排,该方法效果不错,但重排会使得其变慢。

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

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

权衡

牢记协调算法的实现细节非常重要。React可能会在每次操作时渲染整个应用;而结果仍是相同的。为保证大多数场景效率能更快,我们通常提炼启发式的算法。

在目前实现中,可以表明一个事实,即子树在其兄弟节点中移动,但你无法告知其移动到哪。该算法会重渲整个子树。

由于React依赖于该启发式算法,若其背后的假设没得到满足,则其性能将会受到影响:

1.算法无法尝试匹配不同组件类型的子元素。若你发现两个输出非常相似的组件类型交替出现,你可能希望使其成为相同类型。实践中,我们并非发现这是一个问题。

2.Keys应该是稳定的,可预测的,且唯一的。不稳定的key(类似由Math.random()生成的)将使得大量组件实例和DOM节点进行不必要的重建,使得性能下降并丢失子组件的状态。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容