8.Reconciliation

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

React提供了一個(gè)聲明式API怔软,所以你不必?fù)?dān)心每次更新的確切更改近刘。這使得編寫應(yīng)用程序更容易娘荡,但是在React中更新是如何實(shí)現(xiàn)的可能不明顯钞护。本文解釋了我們?cè)赗eact的‘diffing’算法中做出的選擇,以便組件更新是可預(yù)測(cè)养渴,同時(shí)高性能應(yīng)用程序也足夠快雷绢。

Motivation

當(dāng)你使用React時(shí),在單個(gè)時(shí)間點(diǎn)理卑,你可以將該render()函數(shù)想象為創(chuàng)建一個(gè)React元素樹翘紊。在下一個(gè)state或props更新時(shí),該render()函數(shù)將返回一個(gè)不同的React元素樹藐唠。React然后需要找出如何有效地更新UI以匹配最新的樹帆疟。
對(duì)于生成將一個(gè)樹變換成另一個(gè)樹的最小操作數(shù)的算法問題,存在一些通用解決方案宇立。然而踪宠, state of the art algorithms具有大約 O(n3)的復(fù)雜性,其中n是樹中的元素?cái)?shù)量妈嘹。
如果我們?cè)赗eact中使用它柳琢,顯示1000個(gè)元素將需要大約十億此比較。這太貴了润脸。相反柬脸,React基于兩個(gè)假設(shè)實(shí)現(xiàn)啟發(fā)式O(n)算法:

  1. 不同類型的兩個(gè)元素將產(chǎn)生不同的樹。
  2. 開發(fā)者可以暗示毙驯,在具有key prop的不同渲染之間倒堕,哪些子元素是穩(wěn)定的。

在實(shí)踐中尔苦,這些假設(shè)對(duì)于幾乎所有實(shí)際使用情況都是有效的。

The Diffing Algorithm(差分算法)

當(dāng)差分兩棵樹時(shí),React首先比較兩個(gè)根元素允坚。根據(jù)根元素的類型魂那,行為是不同的。

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

每當(dāng)根元素具有不同類型時(shí)稠项,React將拆除舊樹并從頭開始構(gòu)建新樹涯雅。從<a><img>, 或從<Article><Comment>, 或從<Button><div> - 任何這些將導(dǎo)致完全重建。
當(dāng)拆除樹時(shí)展运,舊的DOM節(jié)點(diǎn)被銷毀活逆。組件實(shí)例接收componentWillUnmount()。當(dāng)構(gòu)建新樹時(shí)拗胜,將新的DOM節(jié)點(diǎn)插入到DOM中蔗候。組件實(shí)例接收componentWillMount(),然后componentDidMount()埂软。與舊樹相關(guān)聯(lián)的任何狀態(tài)都將丟失锈遥。
根下面的任何組件也將被卸載并且它們的狀態(tài)被銷毀。例如勘畔,當(dāng)差分時(shí):

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

這將破壞舊的Counter所灸,并重新安裝一個(gè)新的。

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

當(dāng)比較相同類型的兩個(gè)React DOM元素時(shí)炫七,React會(huì)查看兩者的屬性爬立,保留相同的底層DOM節(jié)點(diǎn),并僅更新更改的屬性万哪。例如:

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

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

通過比較這兩個(gè)元素侠驯,React知道只修改底層DOM節(jié)點(diǎn)上的className
更新style時(shí)壤圃,React也知道只更新已更改的屬性陵霉。例如:

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

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

當(dāng)在這兩個(gè)元素之間轉(zhuǎn)換時(shí),React知道只修改color樣式伍绳,而不是修改fontWeight樣式踊挠。
處理DOM節(jié)點(diǎn)后,React然后對(duì)子節(jié)點(diǎn)進(jìn)行遞歸冲杀。

Component Elements Of The Same Type(相同類型的組件元素)

當(dāng)組件更新時(shí)效床,實(shí)例保持不變,so that state is maintained across renders权谁。React更新底層組件實(shí)例的props以匹配新元素剩檀,并且在底層實(shí)例上調(diào)用 componentWillReceiveProps()componentWillUpdate()
接下來旺芽,render()方法被調(diào)用沪猴,diff算法對(duì)前一個(gè)結(jié)果和新結(jié)果進(jìn)行遞歸辐啄。

Recursing On Children(在子元素上遞歸)

默認(rèn)情況下,當(dāng)對(duì)DOM節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行遞歸時(shí)运嗜,React只是同時(shí)迭代這兩個(gè)字節(jié)點(diǎn)列表壶辜,并在有差異時(shí)生成一個(gè)變量。
例如担租,當(dāng)在子元素的末尾添加一個(gè)元素時(shí)砸民,這兩個(gè)數(shù)之間的轉(zhuǎn)換效果很好:

<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>樹岭参。
如果你天真地在開始處插入一個(gè)元素,那么性能會(huì)更差尝艘。例如演侯,這兩棵樹之間的轉(zhuǎn)換效果不佳:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React將改變每個(gè)child,而不是意識(shí)到它課可以保持<li>Duke</li><li>Villanova</li>子樹完好無損利耍。這種低效率會(huì)是一個(gè)問題蚌本。

Keys

為了解決這個(gè)問題,React支持一個(gè)key屬性隘梨。當(dāng)child有key屬性時(shí)程癌,React使用key將原始樹中的child與后續(xù)樹中的child進(jìn)行匹配。例如轴猎,添加key到上面低效的示例可以使樹有效地轉(zhuǎn)換:

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

現(xiàn)在React知道key為‘2014’的元素是新的嵌莉,而key為'2015'和‘2016’的元素只是移動(dòng)了一下。
在實(shí)踐中捻脖,尋找key通常不難锐峭。你要顯示的元素可能已具有唯一的ID,因此key可以來自你的數(shù)據(jù):

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

如果不是這樣可婶,你可以向模型中添加一個(gè)新的ID屬性沿癞,或hash內(nèi)容的某些部分以生成key。
key只需在其兄弟之間是唯一的矛渴,而不是全局唯一的椎扬。
作為最后一種手段,你可以將數(shù)組中的項(xiàng)目索引作為key具温。這可以很好地工作蚕涤,如果項(xiàng)目從來沒有重新排序,但重新排序會(huì)很慢铣猩。

Tradeoffs(權(quán)衡)

重點(diǎn)記住揖铜,reconciliation算法是一個(gè)實(shí)現(xiàn)細(xì)節(jié)。React可以在每個(gè)action上重新渲染整個(gè)應(yīng)用程序达皿;最終結(jié)果將是相同的天吓。我們經(jīng)常細(xì)化啟發(fā)式算法贿肩,以便使常見用例更快。
在當(dāng)前的實(shí)現(xiàn)中龄寞,你可以表達(dá)這個(gè)事實(shí)尸曼,一個(gè)子樹已經(jīng)被移動(dòng)到它的兄弟姐妹中,但你不知道它已經(jīng)被移動(dòng)到別的地方萄焦。該算法將重新渲染該完整子樹。
因?yàn)镽eact依賴于啟發(fā)式算法冤竹,如果不能滿足該算法設(shè)定的假設(shè)拂封,性能將受到影響。

  1. 該算法不會(huì)嘗試匹配不同組件類型的子樹鹦蠕。如果你發(fā)現(xiàn)自己在兩個(gè)具有非常相似輸出的組件類型之間徘徊冒签,你應(yīng)該使它們類型相同。在實(shí)踐中钟病,我們沒有發(fā)現(xiàn)這是一個(gè)問題萧恕。
  2. key應(yīng)該是穩(wěn)定、可預(yù)測(cè)和唯一的肠阱。不穩(wěn)定的key(如由Math.random()產(chǎn)生的)將導(dǎo)致許多組件實(shí)例和DOM節(jié)點(diǎn)被不必要地重新創(chuàng)建票唆,這可能導(dǎo)致子組件性能降級(jí)和丟失狀態(tài)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屹徘,一起剝皮案震驚了整個(gè)濱河市走趋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌噪伊,老刑警劉巖簿煌,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鉴吹,居然都是意外死亡姨伟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門豆励,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夺荒,“玉大人,你說我怎么就攤上這事肆糕“愣眩” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵诚啃,是天一觀的道長(zhǎng)淮摔。 經(jīng)常有香客問我,道長(zhǎng)始赎,這世上最難降的妖魔是什么和橙? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任仔燕,我火速辦了婚禮,結(jié)果婚禮上魔招,老公的妹妹穿的比我還像新娘晰搀。我一直安慰自己,他們只是感情好办斑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布外恕。 她就那樣靜靜地躺著,像睡著了一般乡翅。 火紅的嫁衣襯著肌膚如雪鳞疲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天蠕蚜,我揣著相機(jī)與錄音尚洽,去河邊找鬼。 笑死靶累,一個(gè)胖子當(dāng)著我的面吹牛腺毫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挣柬,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼潮酒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了邪蛔?” 一聲冷哼從身側(cè)響起澈灼,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎店溢,沒想到半個(gè)月后叁熔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡床牧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年荣回,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戈咳。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡心软,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出著蛙,到底是詐尸還是另有隱情删铃,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布踏堡,位于F島的核電站猎唁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏顷蟆。R本人自食惡果不足惜诫隅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一腐魂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逐纬,春花似錦蛔屹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甸箱,卻和暖如春眼刃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摇肌。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工抛蚤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留访得,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓酣衷,卻偏偏與公主長(zhǎng)得像树碱,于是被迫代替她去往敵國(guó)和親肯适。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 參考文章:深度剖析:如何實(shí)現(xiàn)一個(gè)Virtual DOM 算法 作者:戴嘉華React中一個(gè)沒人能解釋清楚的問題——...
    waka閱讀 5,951評(píng)論 0 21
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南成榜,這只是我在學(xué)習(xí)過程中的一些閱讀筆記框舔,個(gè)人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,810評(píng)論 1 18
  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,048評(píng)論 2 35
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評(píng)論 25 707
  • 《流年》 我緊握手中 牽著流年的線 輕輕的 輕輕的 一拉 彈出了一些笑臉 喚起了一份記憶 然后 我在 輕輕的 輕輕...
    舊夢(mèng)未歸閱讀 157評(píng)論 0 0