React組件優(yōu)化篇二 · 調(diào)和過程

如果你能干的父母把你生的天生迎合這個世界,就是莫大的幸福了。萬一沒把你生得適應(yīng)這個世界,那么要么一直忍氣吞聲,要么韜光養(yǎng)晦直至適應(yīng),沒有別的路可走。 ——《我是貓》

兩個virtual dom樹的比對

1.概述

React通過render方法在內(nèi)存中產(chǎn)生一個樹形virtual dom結(jié)構(gòu),這個virtual dom結(jié)構(gòu)會被react處理為一個瀏覽器接受的DOM樹。正是virtual dom的引入,使得react更新性能能夠得到一個較好的表現(xiàn)。

當(dāng)完成了裝載過程的時候,用戶便擁有機(jī)會去引發(fā)界面的更新了。當(dāng)用戶觸發(fā)了界面更新的時候,此時react仍然通過render方法去生成一個新的virtual dom,注意是生成了整棵virtual dom樹,而不是引起變化的那部分。那么接下來的操作難道是利用整棵virtual dom樹轉(zhuǎn)化成DOM樹以供瀏覽器重新渲染嗎?在初次掛載過程自然是這樣的。但是其它情況下那就不是這樣了,畢竟雖然觸發(fā)了頁面的更新過程,但是引起更新的根源有可能就只是頁面的一小部分,因此在頁面發(fā)生更新時直接拿新生成的virtual dom去生成dom樹是非常不應(yīng)該的,至少在性能方面是不能接受的。

那么React在更新過程又是怎么盡力去避免性能問題的呢?答案就是react會提供一個“調(diào)和”過程,這個調(diào)優(yōu)過程會對比新的virtual dom樹以及舊的virtual dom樹,接著找出兩者所不同的地方,根據(jù)不同的地方來修改現(xiàn)有的DOM。那么這個調(diào)優(yōu)過程判具體又是如何判斷兩棵virtual dom樹是不同的呢?


2.調(diào)和過程

當(dāng)React要比較兩棵virtual dom樹的時候,是從根節(jié)點(diǎn)開始遞歸往下對比的。在一棵樹中,實(shí)際上每個節(jié)點(diǎn)都在某種意義上是某些節(jié)點(diǎn)的根節(jié)點(diǎn)。因此,這個對比算法可以從virtual dom樹上的任何一個節(jié)點(diǎn)開始做比較。對于調(diào)和算法來說,首先他從根節(jié)點(diǎn)的類型開始作比較。對于這一步,具有兩個結(jié)果:根節(jié)點(diǎn)的類型是相同的;根節(jié)點(diǎn)的類型是不同的。不同的結(jié)果,對于調(diào)和算法關(guān)于兩個節(jié)點(diǎn)是否相同會有不同的看法。


3.如果根節(jié)點(diǎn)的類型不同的話

如果根節(jié)點(diǎn)的類型不同的話,那么react會認(rèn)為兩個virtual dom樹之間的改變實(shí)在是太大了,會將所有與這個根節(jié)點(diǎn)有關(guān)的子節(jié)點(diǎn)都認(rèn)為是不同的,因此這些無辜者都會被拋棄。那么此時將會經(jīng)歷哪些操作呢?答案就是這些舊節(jié)點(diǎn)的卸載以及新節(jié)點(diǎn)的掛載過程,注意對于此時的這種情況來說,盡管進(jìn)入的頁面的更新過程,但是對于react組件來說卻不會進(jìn)入組件的更新生命周期。

舉個例子:

//before
<div>
  <appHeader />
  <appBody />
  <appFooter />
</div>

//after
<section>
  <appHeader />
  <appBody />
  <appFooter />
</section>

對于上面這個例子來說,我們將無實(shí)質(zhì)性作用的包裹元素由div元素改為了section元素,對于這種情況來說,react會把舊的相關(guān)子節(jié)點(diǎn)(當(dāng)然也包括根節(jié)點(diǎn)自己)給卸載,接著將新的節(jié)點(diǎn)給掛載到相應(yīng)的位置上去。很顯然的,這里實(shí)質(zhì)性的內(nèi)容appHeader等并沒有發(fā)生變化,但是卻還是被強(qiáng)制卸載掛載了一波。

那么如何避免上述這種情況呢?答案是沒有,我們能做就只是避免無意義的更改元素的類型。難道不能通過設(shè)置shouldComponentUpdate來避免這種情況嗎?答案是當(dāng)然不能,因?yàn)檫@種情況下根本就不會進(jìn)入組件的更新生命周期啊。


4.如果根節(jié)點(diǎn)的類型相同的話

注意,但我們比較根節(jié)點(diǎn)的類型的時候是不會比較節(jié)點(diǎn)的屬性的。如果react認(rèn)為根節(jié)點(diǎn)的類型是相同的話,那么此時調(diào)和過程將會認(rèn)為可以重用某些內(nèi)容,因此不會像上述情況中所提到的大刀闊斧的經(jīng)歷卸載過程以及裝載過程,而是只是對組件進(jìn)行更新過程。

具體一點(diǎn)描述的話,我們知道對于react來說,元素分為兩類:dom元素,組件元素。當(dāng)根節(jié)點(diǎn)是dom元素的時候,并且節(jié)點(diǎn)類型相同的話,那么react會比較dom元素上的屬性以及content,如果發(fā)生變化的話,那么將只會在dom上修改相應(yīng)的部分,不會多做某些不必要的更改。

如果根節(jié)點(diǎn)是組件元素的話,并且節(jié)點(diǎn)類型相同的話,那么此時react會比較兩個組件元素上props的不同,利用新得到的props去更新原來的組件實(shí)例,引發(fā)這個組件的更新過程。

更新過程的生命周期函數(shù),我們在前面也提到過,這里在溫習(xí)一下:

  • shouldComponentUpdate
  • componentWillReceiveProps
  • componentWillUpdate
  • render
  • componentDidUpdate

在更新過程中,如果我們的shouldComponentUpdate返回false的話,那么下面的生命周期函數(shù)就不會得到執(zhí)行了,這些函數(shù)當(dāng)然包括了那個挺消耗性能的render函數(shù),因此對于shouldComponentUpdate來說,他就是掌握了react組件優(yōu)化的生殺大權(quán)(當(dāng)然,限于調(diào)和過程認(rèn)為根節(jié)點(diǎn)的類型是相同的情況下)。

在這種情況下,react會接著處理當(dāng)前根節(jié)點(diǎn)的子節(jié)點(diǎn),把它們理解為根節(jié)點(diǎn)接著進(jìn)行同樣的處理。


5.sibling之間發(fā)生變化所引起的調(diào)和處理

在下面這種情況下:

//before
<CommentList>
  <messageItem text="a" />
  <messageItem text="b" />
</CommentList>

//after
<CommentList>
  <messageItem text="c" />
  <messageItem text="a" />
  <messageItem text="b" />
</CommentList>

react處理上面這種情況很出乎意料:他不會認(rèn)為是新添加了一個text props為“c“的messageItem插入到了第一位,而是認(rèn)為原有的text props為"a"的messageitem的props被修改為了"c",并且認(rèn)為原有的text props為"b"的messageitem的props被修改為了"a",接著新增了一個text props為"b"的messageitem。

很奇怪吧,而這種做法帶來了這種問題:那就是原有的無辜的sibling都會進(jìn)行更新過程(畢竟props都發(fā)生了變化能不更新嗎?),而新增得sibling那就是必備的掛載過程了。問題是這里造成了不必要的性能浪費(fèi),畢竟那些其余sibling的內(nèi)容是沒有發(fā)生絲毫變化的。

那么問題來了,如何避免這種情況呢?答案就是利用react 提供組件的key屬性,這個key屬性會被react理解為一個react組件的標(biāo)志,只要你給上述情況的每一個messageItem元素都給添加了一個獨(dú)一無二的key屬性的話,那么我們給messageItem組件設(shè)置的shouldComponentUpdate函數(shù)就能夠如期而至的起作用了。


6.需要注意的地方

當(dāng)使用key屬性的時候,我們必須給他設(shè)置一個獨(dú)一無二的值;其次把數(shù)組的每一項(xiàng)的index設(shè)置為key的值也是錯誤的做法。


END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,104評論 2 35
  • 參考文章:深度剖析:如何實(shí)現(xiàn)一個Virtual DOM 算法 作者:戴嘉華React中一個沒人能解釋清楚的問題——...
    waka閱讀 5,989評論 0 21
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過程中的一些閱讀筆記,個人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,860評論 1 18
  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時,對React的特性、重點(diǎn)和注意事項(xiàng)的提取、精練和總結(jié),可以做為React特性...
    科研者閱讀 8,291評論 2 21
  • 做為從事互聯(lián)網(wǎng)行業(yè)的產(chǎn)品狗一枚,時刻保持著對世界的好奇心十分重要。所以在新年伊始,給自己制定了2016年的讀書計劃...
    哲歌閱讀 254評論 0 0