天天看點

React 應用的性能優化之路

<b>本文講的是React 應用的性能優化之路,</b>

React 應用主要的性能問題在于多餘的處理群組件的 DOM 比對。為了避免這些性能陷阱,你應該盡可能的在shouldComponentUpdate 中傳回 false 。

簡而言之,歸結于如下兩點:

加速 shouldComponentUpdate 的檢查

簡化 shouldComponentUpdate 的檢查

文章中的示例是用 React + Redux 寫的。如果你用的是其它的資料流庫,原理是相通的但是實作會不同。

在文章中我沒有使用 immutability (不可變)庫,隻是一些普通的 es6 和一點 es7。有些東西用不可變資料庫要簡單一點,但是我不準備在這裡讨論這一部分内容。

元件中那些不更新 DOM 的備援操作

DOM 比對那些無須更新的葉子節點

雖則 DOM 比對很出色并加速了 React ,但計算成本是不容忽視的

我們來看一下 React 是如何渲染元件的。

在初始化渲染時,我們需要渲染整個應用

(綠色 = 已渲染節點)

React 應用的性能優化之路

每一個節點都被渲染 —— 這很贊!現在我們的應用呈現了我們的初始狀态。

我們想更新一部分資料。這些改變隻和一個葉子節點相關

React 應用的性能優化之路

我們隻想渲染通向葉子節點的關鍵路徑上的這幾個節點

React 應用的性能優化之路

如果你不告訴 React 别這樣做,它便會如此

(橘黃色 = 浪費的渲染)

React 應用的性能優化之路

哦,不!我們所有的節點都被重新渲染了。

React 的每一個元件都有一個 shouldComponentUpdate(nextProps, nextState) 函數。它的職責是當元件需要更新時傳回true , 而元件不必更新時則傳回 false 。傳回 false 會導緻元件的 render 函數不被調用。React 總是預設在shouldComponentUpdate 中傳回 true,即便你沒有顯示地定義一個 shouldComponentUpdate 函數。

這就意味着在預設情況下,你每次更新你的頂層級的 props,整個應用的每一個元件都會渲染。這是一個主要的性能問題。

盡可能的在 shouldComponentUpdate 中傳回 false 。

簡而言之:

理想情況下我們不希望在 shouldComponentUpdate 中做深等檢查,因為這非常昂貴,尤其是在大規模和擁有大的資料結構的時候。

一個替代方法是_隻要對象的值發生了變化,就改變對象的引用_。

在 Redux reducer 中使用這個技巧:

如果你采用這個方法,那你隻需在 shouldComponentUpdate 函數中作引用檢查

isObjectEqual 的一個實作示例

先看一個_複雜_的 shouldComponentUpdate 示例

如果這樣組織你的資料,會使得在 shouldComponentUpdate 中進行檢查變得_困難_

你可以看出一個非常簡單的資料對應的 shouldComponentUpdate 即龐大又複雜。這是因為它需要知道資料的結構以及它們之間的關聯。shouldComponentUpdate 函數的複雜度和體積隻随着你的資料結構增長。這_很容易_導緻兩點錯誤:

在不應該傳回 false 的時候傳回 false(應用顯示錯誤的狀态)

在不應該傳回 true 的時候傳回 true(引發性能問題)

為什麼要讓事情變得這麼複雜?你隻想讓這些檢查變得簡單一點,以至于你根本就不必考慮它們。

通常而言,應用都要推廣松耦合(元件對其它的元件知道的越少越好)。父元件應該盡量避免知曉其子元件的工作原理。這就允許你改變子元件的行為而無須讓父級知曉這些變化(假設 PropsTypes 保持不變)。它還允許子元件獨立運轉,而不必讓父級緊密的控制其行為。

通過壓平(合并)你的資料結構,你可以重新使用非常簡單的引用檢查來看是否有什麼發生了變化。

這樣組織你的資料使得在 shouldComponentUpdate 中做檢查變得_簡單_

如果你想要更新 interaction 你就改變整個對象的引用

一個建立動态 props 的例子

通常我們不會在元件中建立一個新的 props 把它傳下來 。但是,這在循環中更為常見

這在建立函數時很常見

避免在元件中建立動态的 props

改善你的資料模型,這樣你就可以直接把 props 傳下來

把動态 props 轉化成滿足全等(===)的類型傳下來

eg:

boolean

number

string

如果你實在需要傳遞動态對象,那就把它當作字元串傳下來,再在子級進行解構

如果可以的話,盡量避免傳遞函數。相反,讓子元件自由的 dispatch 動作。這還有個附加的好處就是把業務邏輯移出元件。

在 shouldComponetUpdate 中忽略函數檢查。這樣不是很理想,因我們不知道函數的值是否變化了。

建立一個 data -&gt; function 的不可變綁定。你可以在 componentWillReceiveProps 函數中把它們存到 state 中去。這樣就不會在每一次 render 時拿到新的引用。這個方法極度笨重,因為你須要維護和更新一個函數清單。

建立一個擁有正确 this 綁定的中間元件。這也不夠理想,因為你在層級中引入了一個備援層。

任何其它你能夠想到的、能夠避免每次 render 調用時建立一個新函數的方法。

方案4 的示例

以上列出來的所有規則和技巧都是通過使用性能測量工具發現的。使用工具可以幫助你發現你的應用的具體性能問題所在。

這一個相當簡單:

開始一個計時器

做點什麼

停止計時器

一個比較好的做法是使用 Redux 中間件:

用這個方法可以記錄你應用的每一個 action 和它引起的渲染所花費的時間。你可以快速知道哪些 action 渲染時間最長,這樣當你解決性能問題時就可以從那裡着手。拿到時間值還能幫助你判斷你所做的性能優化是否奏效了。

這個工具的思路和 console.time 是一緻的,隻不過用的是 React 的性能工具:

Perf.start()

do stuff

Perf.stop()

Redux 中間件示例:

CPU 分析器火焰圖表在尋找你的應用程式的性能問題時也能發揮作用。

在做性能分析時,火焰圖表會展示出每一毫秒你的代碼的 Javascript 堆棧的狀态。在記錄的時候,你就可以确切地知道任意時間點執行的是哪一個函數,它執行了多久,又是誰調用了它。—— Mozilla

<b></b>

<b>原文釋出時間為:2016年06月09日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀