說到 react 元件,肯定離不開元件的 props 和 state,我們可以在 props 和 state 存放任何類型的資料,通過改變 props 和 state,去控制整個元件的狀态。當 props 和 state 發生變化時,react 會重新渲染整個元件,元件重新渲染的過程可簡化如下圖:
當元件的 props 或 state 變化,react 将會建構新的 virtual dom,使用 diff 算法把新老的 virtual dom 進行比較,如果新老 virtual dom 樹不相等則重新渲染,相等則不重新渲染。dom 操作是非常耗時的,這導緻重新渲染也非常的耗時,是以要提高元件的性能就應該盡一切可能的減少元件的重新渲染。
假設有一個渲染完成的元件,如下圖:
接下來因為狀态改變,需要重新渲染下圖的綠色的節點,如下圖:
一般的想法是隻需要更新下面的三個綠色節點就能夠完成元件的更新,如下圖:
事實上根據 react 的更新規則,隻要元件的 props 或 state 發生了變化就會重新渲染整個元件,是以除了上述的三個綠色節點以外,還需要重新渲染所有的黃色的節點,如下圖:
除了必要渲染的三個節點外,還渲染了其他不必要渲染的節點,這對性能是一個很大的浪費。如果對于複雜的頁面,這将導緻頁面的整體體驗效果非常差。是以要提高元件的性能,就應該想盡一切方法減少不必要的渲染。
如果一個元件隻和 props 和 state 有關系,給定相同的 props 和 state 就會渲染出相同的結果,那麼這個元件就叫做純元件,換一句話說純元件隻依賴于元件的 props 和 state,下面的代碼表示的就是一個純元件。
<code>shouldcomponentupdate</code> 這個函數會在元件重新渲染之前調用,函數的傳回值确定了元件是否需要重新渲染。函數預設的傳回值是 <code>true</code>,意思就是隻要元件的 props 或者 state 發生了變化,就會重新建構 virtual dom,然後使用 diff 算法進行比較,再接着根據比較結果決定是否重新渲染整個元件。函數的傳回值為 <code>false</code> 表示不需要重新渲染。<code>shouldcomponentupdate</code> 在元件的重新渲染的過程中的位置如下圖:
函數預設傳回為 <code>true</code>,表示在任何情況下都進行重新渲染的操作,要減少重新渲染,隻需要在一些不必要重新渲染的時候,使得函數的傳回結果為 <code>false</code>。如果使得函數的結果一直為 <code>false</code>,這樣不管元件的狀态怎麼變化,元件都不會重新渲染,當然一般情況下沒有人會這樣幹。
react 官方提供了 purerendermixin 插件,插件的功能就是在不必要的情況下讓函數<code>shouldcomponentupdate</code> 傳回 <code>false</code>, 使用這個插件就能夠減少不必要的重新渲染,得到一定程度上的性能提升,其使用方法如下:
檢視源碼後發現這個插件其實就是重寫了 <code>shouldcomponentupdate</code> 方法。
重寫的方法裡面根據元件的目前的狀态群組件接下來的狀态進行<code>淺比較</code>,如果元件的狀态發生變化則傳回結果為 <code>false</code>,狀态沒有發生變化則傳回結果為 <code>true</code>,把這個函數進行進一步的分解其實作如下:
就是分别去比較了函數的 props 和 state 的變化情況。
在 react 的最新版本裡面,提供了 <code>react.purecomponent</code> 的基礎類,而不需要使用這個插件。
假設在每一個元件中都使用 purerendermixin 這個插件,我們來看一下使用這個插件後的狀态的比較過程。假設我們有一個元件如下:
我們想要去改變這個元件的顔色,使其變為 <code>blue</code>,
則狀态的比較就是下面的對象的比較。
上圖的比較是簡單對象的比較,比較過程非常簡單而且快速。但是如果是比較複雜的對象的比較,比如日期、函數或者一些複雜的嵌套許多層的對象,這些會比較耗時,甚至沒法進行比較。
其實我們自己可以重寫 <code>shouldcomponentupdate</code> 這個函數,使得其能夠對任何事物進行比較,也就是<code>深比較</code>(通過一層一層的遞歸進行比較),深比較是很耗時的,一般不推薦這麼幹,因為要保證比較所花的時間少于重新渲染的整個元件所花的時間,同時為了減少比較所花的時間我們應該保證 props 和 state 盡量簡單,不要把不必要的屬性放入 state,能夠由其他屬性計算出來的屬性也不要放入 state 中。
對于複雜的資料的比較是非常耗時的,而且可能無法比較,通過使用 immutable.js 能夠很好地解決這個問題,immutable.js 的基本原則是對于不變的對象傳回相同的引用,而對于變化的對象,傳回新的引用。是以對于狀态的比較隻需要使用如下代碼即可:
這類比較是非常快速的。
假設我們有一個下面這樣的元件:
這是一個可以滾動的表格,<code>offsettop</code> 代表着可視區距離浏覽器的的上邊界的距離,随着滑鼠的滾動,這個值将會不斷的發生變化,導緻元件的 props 不斷地發生變化,元件也将會不斷的重新渲染。如果使用下面的這種寫法:
因為 <code>innertable</code> 這個元件的 props 是固定的不會發生變化,在這個元件裡面使用<code>purerendermixin</code> 插件,能夠保證 <code>shouldcomponentupdate</code> 的傳回一直為 <code>false</code>, 是以不管元件的父元件也就是 <code>outerscroll</code> 元件的狀态是怎麼變化,元件 <code>innertable</code> 都不會重新渲染。也就是子元件隔離了父元件的狀态變化。
通過把變化的屬性和不變的屬性進行分離,減少了重新渲染,獲得了性能的提升,同時這樣做也能夠讓元件更容易進行分離,更好的被複用。
對于嵌套多層、複雜的元件,元件的子節點很多,元件的更新的時間也将花費更多,并且難于維護,資訊流從上往下由父元件傳遞到子元件單向流動,這可能會導緻元件失去我們的控制。
有如下的一個元件(現實中沒人會這樣寫,這隻是一個 demo),元件每 1 秒渲染一次。
通過在 <code>shouldcomponentupdate</code> 函數裡面判斷元件的 children 是否相等決定是否重新進行渲染,由于 children 是 props 的一個屬性,應該每次都是一樣的,元件應該不會重新渲染,可是事實上元件每次都會重新渲染。
讓我們來看一下,children 的具體結構,如下圖:
children 是一個比較複雜的對象,每次元件更新的時候都會重新構造,也就是說 children 是動态建構的,是以每次更新的時候都是不相等的。是以 <code>shouldcomponentupdate</code> 每次都會傳回 <code>true</code>,是以元件每次都會重新渲染。可以用一個變量來代替 children,這樣每次構造的也會是相同的對象。
再來看一個比較費力的做法,如下:
通過在 <code>shouldcomponentupdate</code> 中傳回 <code>false</code>,元件将不會因為外界的狀态變化而發生改變,我們這樣做是因為元件 <code>targetcontainer</code> 和 <code>budgetcontainer</code> 沒有從它們的父元素擷取任何資訊,這樣就不需要管外界的變化,把 children 和父元件進行了隔離,其實<code>twocolumnsplit</code> 就是起了隔離的作用。對于不需要從外界擷取資料的元件,可以通過傳回<code>false</code> 來隔離外界的變化,減少重新渲染。
我們也可以通過元件的容器來隔離外界的變化。容器就是一個資料層,而元件就是專門負責渲染,不進行任何資料互動,隻根據得到的資料渲染相應的元件,下面就是一個容器以及他的元件。
容器不應該有 props 和 children,這樣就能夠把容器自己和父元件進行隔離,不會因為外界因素去重新渲染,也沒有必要重新渲染。
設想一下,如果設計師覺得這個元件需要移動位置,你不需要做任何的更改隻需要把元件放到對應的位置即可,我們可以把它移到任何地方,可以放在不同的應用中,同時也可以應用于測試,我們隻需要關心容器的内部的資料的來源即可,在不同的環境中編寫不同的容器。
purity => use shouldcomponentupdate & purerendermixin
data comparability => use highly comparable data (immutability)
loose coupling => use for maintainability and performance
children => be careful of children, children create deep update,
<a href="https://www.youtube.com/watch?v=kyzlprvwz6c&feature=youtu.be&t=1372" target="_blank">making your app fast with high-performance components</a>
<a href="https://facebook.github.io/react/docs/advanced-performance.html" target="_blank">react advanced-performance</a>
<a href="http://gold.xitu.io/entry/57621f7980dda4005f7332f3" target="_blank">react 應用的性能優化</a>
轉載自:http://taobaofed.org/blog/2016/08/12/optimized-react-components/
作者:慎裡