黑帕雲版權全部,未經容許,禁止轉載
各位好,這裏是黑帕雲技術博客,我是黑帕雲的軟件開發工程師毛超。在這裏,咱們會把不按期的分享開發黑帕雲過程當中值得總結的知識,經驗,最佳實踐,甚至是教訓。但願經過技術博客,讓更多的工程師認識咱們,認識黑帕雲。前端
慣例來一個廣告吧,黑帕雲 https://hipacloud.com —— 新一代的數據協做平臺,讓任何人都能經過最熟悉的技能,構建知足其需求的工具,使軟件建立民主化。你值得擁有:)做爲程序員,也能夠用黑帕雲方便快捷的搭建本身的數據管理中心,不必定什麼東西都要本身動手寫代碼,模板中心有很多好用的應用,值得一看。react
在Reactjs大行其道的今天,前端性能優化彷佛與開發者愈來愈遠,由於 React 確實很快。React憑藉着 Virtual DOM 的抽象,讓開發中只關注組件中的 state 和 props,框架本身操做瀏覽器更新 DOM,達到最佳性能。回想起當年初識 React,曾被這個「大膽的想法」驚的「直呼內行」,不過如今想一想,有那麼點相似於經典的「指針段子」:C++說,指針過重要了,必定要讓程序員本身管理。Java說,指針過重要了,必定必定不能讓程序員本身管理(聽懂鼓掌)。git
但 React 也不是萬能的,在某些場景下,React 也會很慢,慢的使人髮指。問題不外乎兩種:要麼就是開發人員本身犯了錯誤,要麼就是你的業務場景已經超出了 React 能處理的範圍。惋惜在絕大多數狀況下,都是開發本身的問題。下面經過一個真實的案例,分享一下黑帕雲前端性能調優的故事。程序員
性能 bug 比功能 bug 更難以察覺
首先簡單的介紹一下黑帕雲中的基本概念:github
圖0. 「工資表管理應用」中的「工資表」數據web
某天,黑帕雲的 CEO 米高給我反饋,說他在應用中切換數據表時不夠流暢,沒有那種「絲般順滑」的感受,而且在數據較多的時滯後感更加明顯,讓我想一想辦法解決。chrome
當時的第一反應是有點懵逼的,CEO 感受不夠「順滑」,但是我感受挺好的呀,這玩意見仁見智怎麼搞(╯‵□′)╯︵┻━┻redux
冷靜分析以後,想起來頁面的幀率就能夠度量頁面的順滑度。提及幀率有的同窗可能有點陌生,但提及 FPS 你確定聽過。FPS (frame per second) 每秒幀數,簡單來講就是每秒顯示多少個畫面。FPS 的值越高,頁面越流暢。在這裏我推薦 FPS extension,一個chrome的插件,能夠很是方便的顯示頁面實時幀率。後端
測試以後發如今表格切換的時候,幀率會急速降低到個位數,難怪米高會以爲不夠流暢(最優幀率是60,越高越好)。這種卡頓大機率是在更新 DOM 時發生了什麼,阻塞 UI 渲染線程致使的,我得去看看代碼了。
圖1.出現了「幀率深淵」,並伴隨嚴重的卡頓,體驗不夠好
不被別人罵 WTF 的代碼就是好代碼
過了一遍代碼,加載數據表頁面邏輯大體以下:
圖2. 加載數據表的流程
從代碼中沒有發現什麼有價值的線索,初步說明開發沒有犯低級錯誤致使性能問題。那麼根據上述代碼邏輯,有多是下面動做慢了:
經過 chrome 的 network 看到請求並不慢(給後端同窗甩鍋時要有理有據),那麼首先懷疑 Reducer 函數吧。
一項工做若是你沒法度量他,你就沒法優化他
根據 React 的定義,Redux Action 是一個簡單的 js 對象,用來觸發 Reducer 函數更改 Redux Store 裏面的數據。在用戶切換數據表時 dispatch 了不止一個 action ,須要找出最慢的那一個。NPM包 redux-perf-middleware 是 一個redux 的 ,能夠在瀏覽器的console中輸出處理每個action 的時間。
AvraamMavridis/redux-perf-middleware
使用起來也很是簡單,加載redux的middleware裏面就能夠了
//記得只在dev環境下使用 import reduxPerfLogger from 'redux-perf-middleware'; const middleware = process.env.NODE_ENV !== "production" ? [reduxPerfLogger ,getDefaultMiddleware()[1]] : getDefaultMiddleware();
安裝完成以後刷新頁面,在瀏覽器裏測試了一下來回切換5000條Records的數據表,就能夠看到輸出結果。
圖3. redux-perf-middleware在瀏覽器console的輸出結果
能夠看處處理 getTableInitialRecordsSuccess action 花了快 400ms,難怪幀率只有個位數。Review 代碼時沒以爲 Reducer 裏面有什麼特殊的邏輯,不該該這麼慢,看來咱們須要進一步找出 Reducer 的性能熱點。
若是要把頁面幀率優化到最優的 60 幀,那就意味着頁面一次刷新時間不能超過 1000ms / 60 = 16.67 ms。
要想知道某一段邏輯哪裏慢,固然能夠經過 console.log 打印處理時間,不過我更加推薦使用 chrome devtool 中的 performance 工具,能夠很是方便找出頁面某一段時間內的頁面的性能相關數據。
使用方法也很簡單,打開你想要測試的網頁,打開 chrome 的 devtool ,選中 performance,而後點擊下面的錄製按鈕,接着在頁面開始操做,操做完成後點擊結束按鈕,就能看到分析結果了。切記要用打包壓縮事後的js跑,而且保證瀏覽器沒有開啓任何多餘的插件,保證環境的乾淨對測試很重要!
圖4. chrome performance的輸出結果
從圖4中能夠看到,最上面的一塊區域是整體概覽的時間軸,記錄了測試過程當中的起點和終點(藍色方框中突出的紅色小點就是瀏覽器發現的卡頓現象)。中間一塊區域包括了網絡調用狀況,主線程,頁面交互等數據。最下面一塊區域是各類總結圖表,能夠看到在我切換數據表的 4898ms 裏,JS 一共花費了 2396ms。(你們有興趣的話我再單開一篇好好講講 performance 工具)
咱們能夠經過改變時間軸的起點和終點選中感興趣的一段時間內瀏覽器的性能數據,好比我選中了「數據表內容變化」的一段時間,重點查看 JS 運行狀況,
圖5. 重點觀察「數據表內容變化」時間段的JS運行狀況
在圖5中最下面的表格顯示了JS的運行狀況:
第一列是 Self Time,指的是完成函數當前的調用所需的時間,僅包含函數自己的聲明,不包含函數調用的任何函數。
第二列是 Total Time,指的是完成此函數和其調用的任何函數當前的調用所需的時間。
舉個例子,下面的foo函數,Total Time 是 35ms,Self Time 是 15ms。
const foo = ()=> { //foo本身的邏輯,花了10ms bar(); // 調用bar函數,花了20ms //foo本身的邏輯,花了5ms }
按照 Self Time 排序,會更容易找到性能熱點。經過表格的第一項就找到了 records.ts 文件(啪的一下,就找到了,很快呀),就是 Records Reducer 的處理文件,從調用鏈看,問題出如今調用 lodash includes 方法上,一共花了 462ms
圖6. 定位到 records.ts 文件的性能問題,一般方法的調用棧比較深,須要有點耐心找找有沒有應用本身的方法或者文件。
好,那咱們打開代碼,看看調用 includes 方法的上下文。經過分析發現是這麼作的
圖7. Records Reducer中的示例代碼
仔細分析一下就會發現,這種作法在大數據量下確實有問題。假設 store 中已經存在 m 條 records,那麼在處理新返回的 n 個 records時,updateRecord 方法就會在 m 個元素的數組上執行 n 次 includes。假設 m 和 n 都是5000的話,計算量仍是很恐怖的。
接,化,發
如何破解呢?結合業務場景思考一下,當咱們切換表時後端總會返回該表全量的數據,因此是不須要用 includes 判斷「是否存在過」,咱們能夠直接經過全量數據生成一個新的數組,而後覆蓋原來的老數組,就避免了重複調用 includes。
圖8. 新方案的示例代碼
和代碼的原做者溝通了一下,發現最初 updateRecord 是爲了更新某一個 record 設計的,用在「更新全表 records 」只是爲了代碼複用而已,新方案明顯更優。
優化以後效果仍是很不錯的,沒有了「幀率深淵」,chrome performance 中也沒有明顯的慢方法
圖9. 優化後的FPS
也許有同窗會問爲何切換數據表時 api 後端永遠返回全量數據而不是增量數據?這是一個好問題,簡單一點回答,其實黑帕雲已經支持了,當數據量超過某個閾值後,就會開啓增量加載模式,保證超大數據量下的使用體驗。
萬事開頭難,而後中間難,最後結尾難
修改上線以後,CEO反饋「切換數據表順滑多了」,總算是有所改善:)
回顧一下上面的工做,思考以後值得分享的是:
好,這一篇就寫到這裏,算是一個開胃菜。下一篇我會繼續分享更多幹貨,包括 react profiling 工具的使用,redux store 設計,減小React組件重複刷新等最佳實踐,敬請期待。
工做地點: 成都/西安
咱們提供什麼?
感興趣的小夥伴們能夠經過如下方式投遞簡歷;
將簡歷發送至: job@hipacloud.com
在線簡歷投遞:https://lyv12j.hpapps.cn/forms/ln2je3
感謝你們的閱讀。