
壹 ❀ 引
我們公司產品主要提供企業項目管理服務,那么自然有配套的desk
工單管理系統,用於搜集客戶bug
以及相關問題反饋。有一天我在測試功能時碰巧發現了一個bug
,所以就想着提一個工單記錄下方便日后修復。但就在創建工單填寫標題時我發現標題輸入卡爆了,本想着創建一個bug
工單,結果又附帶新建了一個性能bug
單....下圖便是當時問題表現:

可以看到我只要連續操作,輸入框都存在非常明顯的卡頓,輸入並不流暢,那么本文主要記錄此問題的排查思路,以及部分性能排查小技巧,本文開始。
貳 ❀ 排查過程
因為操作卡頓明顯,出於好奇,我打開了瀏覽器的Performance monitor
工具,此工具能時時記錄瀏覽器CPU
占用情況以及JS堆內存
使用情況,具體入口見下圖:

於是我嘗試在標題欄連續輸入或者連續刪除,黃色區域非常明顯的像一座山峰一樣升了起來,如下圖可見CPU
占用甚至離譜的飆升到了100%
,JS
堆內存也從200M不到升到700多,我這還是M1
芯片16g
內存的mac
.....確實性能堪憂。

經過簡單測試對比,我發現如果我慢慢的輸入,那么黃色區域就是一座座小山峰,會卡,但是不會卡那么久。但如果我連續輸入,那么黃色區域就直接形成一個梯形了,所以這里就可以斷定輸入框沒做防抖操作,如果有防抖,連續輸入表現應該和我有間隔的輸入表現一致。
上述分析也只是推斷出沒防抖,為什么這么卡?這就要需要通過Performance
來具體分析了,打開控制台Performance
,點擊⏺開始錄制,然后重復上述連續輸入的操作,關閉錄制,等待少許片刻,我們就能看到具體的火焰圖以及相關性能分析了,具體如下:

通過最下方的Summary
可以看到整個過程持續4.45秒,而Script相關耗時就占用了3.19秒,說明主要是JS相關的邏輯在占用耗時。
將目光放到截圖的火焰圖區域,也就是黃色梯形部分,這里我一共連續操作了2次,所以形成了2端梯形,而在具體調用棧部分,我們能發現每段梯形正好對應一大段的Task
;比如第一段操作我連續輸入了4個字符,所以下面生成了4個Task
任務,第二段我連續輸入了5次,因此有5個Task
任務,這也驗證了前面沒做防抖的推斷,每次輸入都需要執行大量javascript
邏輯,那么具體是什么在耗時呢?
我們可以選中黃色梯形區域,盡量具體到一個Task
任務范圍,然后拖動調用棧來看看一個Task
任務到底在搞什么鬼:

當拖到調用棧底部,我們發現一個Task
任務下面都對應了四次render
(縱向從上到下是調用過程,橫向是耗時占用分布),搞半天每輸入一次都執行了4大段的render
。注意,這里的四次render
並不是表示一共只渲染了四次,考慮到時間切片的概念,chrome
應該是把數百次render
合成了一小段,其實這四段render
加起來可能是由數百次render
組合而成。
為了驗證這一點,我們打開React Developer Tools
工具,選擇設置:

勾選General
下的Highlight updates when components render.
作用就是當組件重新render
時都會標記高亮。

然后再次在標題輸入框輸入內容,隨后我人傻了....這個彈窗組件中數十個input
、select
,會隨着每次標題輸入框值變化,都會重新渲染多次:

我從寫這塊邏輯的同學那了解到,這個組件在設計上確實存在問題,整個創建desk
的彈窗在數據層其實都只依賴了一個this.state.form
對象,來搜集整個彈窗中每個屬性的值,大概如下:

了解react
的同學都知道,只要state
發生變化,組件肯定得重新渲染,所以這個組件設計上就沒有做數據分塊,導致一個iuput
變化引起整個組件都重新渲染的尷尬局面,那么到這里我們知道了卡頓的第二個原因,數據設計上沒做數據分塊,對於底層組件在是否需要渲染上也沒做判斷。
在后續溝通中我得知,這個組件導致大量渲染的問題是一直存在的,說明引發這次卡頓的根因還不是組件設計的問題(雖然確實有問題),那為何之前渲染這么多都不卡,怎么現在輸入一次就卡這么久,於是我繼續跟調用棧,點擊綠色區域,可以看到對應的文件:

首先文件引用來自ones-design
這個三方庫,這個庫是我們公司自己基於antd
封裝的一套組件庫,說明最終耗時都跟這個文件有關,點擊core.es.js
就能看到具體代碼執行塊:

option.foEarch
??難道跟select
組件的options
數量有關?在與組件庫的同學溝通了解,這塊邏輯還真是在對select
選項做遍歷操作。而很不巧的是,因為我們自己項目desk
工單創建上有一個叫客戶信息的select
,這個下拉框包含了數以萬計的下拉選項,options
選項數據非常龐大....
而之前不卡是因為我同事在下拉框這里使用了虛擬列表的select
,不管你多少選項,我固定只渲染十幾條,所以面對數萬條的數據,也能流暢展示。
那么到這里也驗證了我最初排查的一個猜想,因為剛發現這個問題時候,我就想着能不能在我本地開發環境復現,於是我也在新建工單設置那配置了數十個屬性,但是標題輸入依舊非常流暢,當時我就猜想是不是跟select
相關組件的options
有關。而這個性能問題看樣子也持續了一段時間了,為什么沒有客戶反饋這個問題呢?其實也是因為客戶側很難像我們這樣有這么龐大選項的select
。
於是我又跟蹤了一下這個組件最近的修改記錄,最后發現其實就是前兩周我一個同事,為了解決select
樣式問題,將之前就有的虛擬列表select
(之前那個select有樣式缺陷)替換成了我們自己最新的組件庫的select
,從而導致了這個問題。
叄 ❀ 解決方案
那么到這里,我們統計一下排查下來發現的問題:
- input輸入未做防抖,修改成本較少
- 創建工單組件設計不合理,需要做數據拆分,修改成本大,需要重構
- 在工單組件下層組件添加是否需要更新的判斷,比如將
React.Component
改為React.PureComponent
,起碼能優化一點 - 組件庫優化
select
,對於options
操作做緩存,不要每次渲染都重復遍歷
那么最終修改方案采用了第三條與第四條,當時引發性能問題的同學,也只是修改了少量組件的創建方式為React.PureComponent
,就發現創建上流暢了不少,組件庫那邊也緊急上了一個hotfix
,對options
操作了做了緩存,遍歷一次后之后的重復渲染都不引起二次遍歷。
那么下圖就是當時修改完成的效果了,可以看到我的輸入刪除都非常流暢了,雖然在組件創建階段這個select
其實還是會卡頓一會,但是用戶已經很難感知到。

那么到這里本文結束!