8.Reconciliation

React版本:15.4.2
**翻译:xiyoki **

React提供了一个声明式API,所以你不必担心每次更新的确切更改。这使得编写应用程序更容易,但是在React中更新是如何实现的可能不明显。本文解释了我们在React的‘diffing’算法中做出的选择,以便组件更新是可预测,同时高性能应用程序也足够快。

Motivation

当你使用React时,在单个时间点,你可以将该render()函数想象为创建一个React元素树。在下一个state或props更新时,该render()函数将返回一个不同的React元素树。React然后需要找出如何有效地更新UI以匹配最新的树。
对于生成将一个树变换成另一个树的最小操作数的算法问题,存在一些通用解决方案。然而, state of the art algorithms具有大约 O(n3)的复杂性,其中n是树中的元素数量。
如果我们在React中使用它,显示1000个元素将需要大约十亿此比较。这太贵了。相反,React基于两个假设实现启发式O(n)算法:

  1. 不同类型的两个元素将产生不同的树。
  2. 开发者可以暗示,在具有key prop的不同渲染之间,哪些子元素是稳定的。

在实践中,这些假设对于几乎所有实际使用情况都是有效的。

The Diffing Algorithm(差分算法)

当差分两棵树时,React首先比较两个根元素。根据根元素的类型,行为是不同的。

Elements Of Different Type(不同类型的元素)

每当根元素具有不同类型时,React将拆除旧树并从头开始构建新树。从<a><img>, 或从<Article><Comment>, 或从<Button><div> - 任何这些将导致完全重建。
当拆除树时,旧的DOM节点被销毁。组件实例接收componentWillUnmount()。当构建新树时,将新的DOM节点插入到DOM中。组件实例接收componentWillMount(),然后componentDidMount()。与旧树相关联的任何状态都将丢失。
根下面的任何组件也将被卸载并且它们的状态被销毁。例如,当差分时:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

这将破坏旧的Counter,并重新安装一个新的。

DOM Elements Of The Same Type(相同类型的DOM元素)

当比较相同类型的两个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然后对子节点进行递归。

Component Elements Of The Same Type(相同类型的组件元素)

当组件更新时,实例保持不变,so that state is maintained across renders。React更新底层组件实例的props以匹配新元素,并且在底层实例上调用 componentWillReceiveProps()componentWillUpdate()
接下来,render()方法被调用,diff算法对前一个结果和新结果进行递归。

Recursing On Children(在子元素上递归)

默认情况下,当对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将改变每个child,而不是意识到它课可以保持<li>Duke</li><li>Villanova</li>子树完好无损。这种低效率会是一个问题。

Keys

为了解决这个问题,React支持一个key属性。当child有key属性时,React使用key将原始树中的child与后续树中的child进行匹配。例如,添加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知道key为‘2014’的元素是新的,而key为'2015'和‘2016’的元素只是移动了一下。
在实践中,寻找key通常不难。你要显示的元素可能已具有唯一的ID,因此key可以来自你的数据:

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

如果不是这样,你可以向模型中添加一个新的ID属性,或hash内容的某些部分以生成key。
key只需在其兄弟之间是唯一的,而不是全局唯一的。
作为最后一种手段,你可以将数组中的项目索引作为key。这可以很好地工作,如果项目从来没有重新排序,但重新排序会很慢。

Tradeoffs(权衡)

重点记住,reconciliation算法是一个实现细节。React可以在每个action上重新渲染整个应用程序;最终结果将是相同的。我们经常细化启发式算法,以便使常见用例更快。
在当前的实现中,你可以表达这个事实,一个子树已经被移动到它的兄弟姐妹中,但你不知道它已经被移动到别的地方。该算法将重新渲染该完整子树。
因为React依赖于启发式算法,如果不能满足该算法设定的假设,性能将受到影响。

  1. 该算法不会尝试匹配不同组件类型的子树。如果你发现自己在两个具有非常相似输出的组件类型之间徘徊,你应该使它们类型相同。在实践中,我们没有发现这是一个问题。
  2. key应该是稳定、可预测和唯一的。不稳定的key(如由Math.random()产生的)将导致许多组件实例和DOM节点被不必要地重新创建,这可能导致子组件性能降级和丢失状态。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • 参考文章:深度剖析:如何实现一个Virtual DOM 算法 作者:戴嘉华React中一个没人能解释清楚的问题——...
    waka阅读 5,959评论 0 21
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,820评论 1 18
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,055评论 2 35
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,925评论 25 707
  • 《流年》 我紧握手中 牵着流年的线 轻轻的 轻轻的 一拉 弹出了一些笑脸 唤起了一份记忆 然后 我在 轻轻的 轻轻...
    旧梦未归阅读 158评论 0 0