其實這篇文章是要為 不要只因為性能考量而選擇 React.js 中對 React 操作大量的 DOM 的效能辯白,是的 React 沒有你想像中的快,但是其實也沒有文章中的 例子 那麼誇張,文章中的例子是在開發環境下跑的,我實際上測試全選 10000 個 checkbox 在 production 環境情形下速度可以比開發環境模式快上 4 ~ 5 倍左右
這篇文章會比較一些提高速度的作法,並實際量測 js 運行時間
- 一般沒有下環境變數的 Dev 模式
- 下環境變數 NODE_ENV = production
- 減少 Component 的抽象數量
- 用 Redux 減少 Reconciliation 的 Component 數量
這個實驗code在此 react-long-list 你可以自行 clone下來跑看看,以下是在 chrome canary 下開 Timeline 觀察到的 js 執行時間
initial time select all select one
---------------------------------------------------------
dev 3150 ms 1960 ms 1860 ms
---------------------------------------------------------
production 827 ms 314 ms 241 ms
---------------------------------------------------------
no-Abstract 734 ms 164 ms 160 ms
---------------------------------------------------------
Redux 1150 ms 359 ms 78 ms
---------------------------------------------------------
不過其實這個例子的效能沒有那麼重要,畢竟一個頁面設計 10000 個 checkbox 蠻有病的,效能不是工程師要考慮的唯一問題,其他還有維護與同事協作的方便性,運行容不容易出錯,出錯容不容易除錯,而大部分的台灣老闆只在乎開發的速度跟功能的完成,效能上在使用不要太誇張老闆通常也不會很介意(嘆)
在 Dev 模式下開發
在一般的情形下跑
npm run dev
初始化 10000 個 checkbox 的 js 運行時間約為 3.15 s
全選 10000 個 checkbox js 運行時間大約是 1.96 sec
單選 1 個 checkbox js 運行時間大約為 1.86 秒
NODE_ENV = production
如果你在 React Github 的官方 Repository 搜尋 __DEV__ 這個變數,可以發現大概有 70 個搜尋結果,有許多 type 還有參數的 validate 的檢查 warning都被夾住在 if (__DEV__) 的判斷式下來幫助我們開發,官方在 build code 到npm 上的時候會用 babel 把 __DEV__ 這個變數用 process.env.NODE_ENV !== ‘production’ 取代,所以如果用在用 webpack 打包 code 的時候,記得要用 DefinePlugin 把環境變數設成 production
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production'),
},
}),
另外 UglifyJS 的時候把 Dead Code compression 打開就會直接移除整個 if(false) code block
new webpack.optimize.UglifyJsPlugin({
compress:{
dead_code: true,
}
})
用這樣的設定跑
npm run production
初始化 10000 個 checkbox 的 js 運行時間約莫是 827 ms
全選 10000 checkbox 的 js 運行時間為 314 ms 左右
單選一個 checkbox 的 js 運行時間 241 ms 左右
任何的 Abstraction 都會造成效能上的損失
原本的架構上把 checkbox 做成一個 component ,但是其實越多的Component 都會造成對效能產生損失,因此這次我們把 checkBox Component 拆掉,直接在 App 裡面 render function map 出所有的 checkbox,運行
npm run no-abstract
實驗結果如下
初始化 10000 個 checkbox js 運行時間約為 734 ms
全選 10000 checkbox 的 js 運行時間 164 ms 左右:
單選 1 個 checkBox 的 js 運行時間 160 ms 左右:
用 Redux connect 每一個 checkbox 減少不必要的 Reconciliation 成本
React 從上層 Component seState 會造成下方所有的 Components 都會進到Reconciliation 的過程來決定要不要更新,因此我想如果一開始就不要進入Reconciliation 會不會速度會更快一點,因此我就改用 Redux 去托管 state,react-redux 用 connect 包住 checkbox 後 call setState 的就會變成 connect 裡頭的 higher order component ,而非原本的上層 Component,這樣進入 Reconciliation 的 Component 只有被點選的 checkbox 。 但是其實這樣會不會加速其實也很難說,redux 本身也是會有 reducer 跟 listener array 需要跑的成本,運行
npm run redux
實驗結果如下:
初始化 10000個 checkbox 的 js 運行時間 1.15 s
全選 10000 checkbox 的 js 運行時間 359 ms 左右:
單選一個 checkbox 的 js 運行時間 78.4 ms,結果看起來單選的確提昇不少:
結論
production build 的時候一定記得要設 NODE_ENV= production
其他的優化還要考慮其他架構上的設計,不見得一定要追求到這種地步,也許你也沒有如此大量的 component 要 update,幫助可能更有限,大家參考一下就好