<b>本文講的是React 應用的性能優化之路,</b>
React 應用主要的性能問題在于多餘的處理群組件的 DOM 比對。為了避免這些性能陷阱,你應該盡可能的在shouldComponentUpdate 中傳回 false 。
簡而言之,歸結于如下兩點:
加速 shouldComponentUpdate 的檢查
簡化 shouldComponentUpdate 的檢查
文章中的示例是用 React + Redux 寫的。如果你用的是其它的資料流庫,原理是相通的但是實作會不同。
在文章中我沒有使用 immutability (不可變)庫,隻是一些普通的 es6 和一點 es7。有些東西用不可變資料庫要簡單一點,但是我不準備在這裡讨論這一部分内容。
元件中那些不更新 DOM 的備援操作
DOM 比對那些無須更新的葉子節點
雖則 DOM 比對很出色并加速了 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 -> 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>