[譯] 用 React Hooks 和調試工具提高應用性能

在構建 React 應用時,你會發現隨着嵌套組件增多,用戶界面的某些部分開始變得緩慢遲滯。這是由於,被改變 state 的元素在組件樹中的層級越高,瀏覽器須要重繪的組件越多。javascript

本文將告訴你如何經過備忘(memoization)技術避免沒必要要的重繪,讓你的 React 應用快如閃電。⚡html


CLEVER°FRANKE 的一個客戶端項目中,我作過一個過濾器組件,該組件包含一個展現步數的直方圖。前端

直方圖過濾器組件

我發現每當拖拽過濾器的操縱桿,動畫幀率就會驟降,致使組件失去效用。故此我決定一探究竟。java

抽絲剝繭

只有明白用戶拖拽操縱桿時的內部運做原理,才能肯定從何處下手調試。React 使用虛擬 DOM 來表明 DOM 中真實的元素。每當用戶操做界面元素,應用的 state 都會改變。React 會遍歷全部受 state 改變影響的組件,計算生成新的虛擬 DOM。React 將新舊版本的虛擬 DOM 進行比較,若發現兩者有差別,就將對應的變化更新到真實 DOM 上。該過程叫作 reconciliationreact

操縱 DOM 元素但是一個很是耗費資源的任務。一樣,遍歷全部受影響組件的 render 函數也很耗時,render 函數中的計算量很大時尤爲如此。所以咱們要儘可能減小這些浪費性渲染android


如今回到咱們的案例:由於過濾器組件的 state 由其父組件掌控,因此個人推論是可能發生了沒必要要的渲染和計算。爲了快速確診,咱們要使用 Chrome 調試工具。它有個 Paint Flashing 功能,能夠將發生改變的 DOM 高亮顯示。你能夠在 Rendering 標籤頁臨時激活該功能:ios

在 Chrome 調試工具中激活 Paint Flashing 功能

一經激活,瀏覽器就會顯示哪些元素被重繪了。在本案例中效果以下:git

用 Paint Flashing 功能高亮過濾器組件

看起來合情合理,只有我操縱的組件引起了 DOM 操縱。也就是說瀏覽器沒有作沒必要要的繪製。那咱們就要進一步深刻來探究緣由了。github


爲了把 React 組件重繪的狀況看得更真切,咱們得用 React 調試工具中一個相似的工具。它叫作 Highlight Updates,你能夠在 React 調試工具的首選項面板中找到它。激活後,它會高亮顯示全部正在渲染的組件。若是渲染時間過長,它還會用特殊顏色標識出來。web

用 Highlight Updates 功能高亮過濾器組件

React 調試工具使你可以檢查 React 組件層級,以及對應組件的 props 和 state。
它有瀏覽器插件(支持 ChromeFirefox)和獨立應用(支持 Safari、IE、和 React Native 等運行環境)兩種形式。


這裏就清晰地揭示了問題所在:當我在一個過濾器上拖拽,包含直方圖的另外一個過濾器也被重繪了。 這就是應該被避免的處理器資源浪費。像直方圖這樣的笨重組件尤爲如此。

如今咱們知道了問題所在,但還不知道致使界面響應緩慢的緣由。爲了找到緣由,咱們可使用 Chrome 調試工具的 Performance 面板。它能夠幫助你查看在瀏覽器在執行某一特定任務的過程當中,每一幀具體作了什麼。

關於 Performance 面板的使用細節,不在本文討論範圍以內。但你能夠在這裏找到教程。


我使用 Performance 面板記錄了過濾器組件中的一次變動。放大火焰圖後,我有了如下發現:

Performance 面板中的火焰圖 🔥

如你所見,這兩個火焰圖大致相同。第一張圖(在 Timings 下方)展現了 React 組件的實際的加載和更新。React 調用了用戶時間接口,因此咱們能看到這張額外的圖。第二張圖展現了主線程上執行的全部任務,這張圖更爲詳細。

我更喜歡用第一張圖來看哪些組件性能差,用第二張圖深刻了解具體哪一個函數和計算過程耗時更多。


第一次看到 Performance 面板的默認火焰圖,你可能會被嚇到。萬幸 React 調試工具也有一個類似功能,在 Profiler 標籤頁中,可以根據用戶時間接口生成一樣的火焰圖。我認爲 React 調試工具中的火焰圖更易於理解,並且它還有不少趁手的附加功能:

  • 你能夠根據組件渲染時長將全部組件排序(見下方截圖)。
  • 你能夠快速瀏覽不一樣的渲染記錄。
  • 你能夠點擊某組件查看特定渲染階段的 props

按渲染時長排列組件


以上圖形揭露了罪魁禍首:Histogram。特別是渲染第二個直方圖(右側那個),耗費了很長時間(402.8 毫秒!),即便我根本沒有拖拽它。咱們破案了!接下來就該修復問題、優化組件性能了。

注意:我記錄性能時打開了 CPU 節流功能,用 1/4 倍速模擬那些並不是使用最新版 Macbook Pro 的用戶,以此來突顯性能問題。

提高組件性能

爲防止浪費性渲染的發生,咱們能夠經過備忘技術優化組件。咱們要使用 React.memo 來記憶組件,用 React 的備忘 hooks useMemouseCallback 記憶變量和函數。

React.memo

16.6.0 版本起,React 就支持高階組件 React.memo 了。它等價於 React.PureComponent,但只適用於函數組件。社區正逐步從 class 的組件風格轉向帶有 hooks 的函數組件風格,而 React.memo 正是這種組件。

當你用 React.memo 包裹一個函數組件時,它會將傳入的 props 進行淺層比較。當比較的 props 不一致時,纔會從新渲染組件。你也能夠本身寫一個比較函數,做爲第二個參數傳入。但要慎用,以免意外故障。

咱們能夠將組件分解爲更小的組件,並把每一個更小的組件都用 React.memo 包裹起來。如此你能保證當 props 更新,僅有組件的一部分從新渲染了。但也不要把全部東西都作備忘,由於比較 props 所花時間可能要比渲染組件的時間還要長。

在本文案例中,我用 React.memo 包裹了過濾器組件(RangeSlider)和 Histogram 組件。此外,我把直方圖分解爲包裹組件和 HistogramBuckets 組件兩部分,將邏輯部分和展示部分剝離開來。

const RangeSlider = React.memo(props => {
   ...
});
複製代碼

備忘 hooks

React 16.8.0 版本爲咱們帶來功能強大的 hooks,有了 hooks,咱們能夠輕鬆備忘組件中的值和回調函數。在引入 hooks 以前,你固然也能夠用一個單獨的庫實現備忘功能,但自從它成爲 React 原生庫的一部分,集成和塑造工做流變得更加簡單易行。

useMemo 會記憶一個值,這樣就不用在下一輪渲染中從新計算它了。useCallback 記憶的則是回調函數。你能夠給兩者傳入一個依賴數組,該數組包含了組件做用域的值(好比 props 和 state),這些值將在 hooks 內部被用到。每次渲染時,React 都會比較這些依賴值,一旦它們發生改變,React 就會更新備忘的值或函數。

注意:React 爲了儘量快地進行比較,使用了比較算法 Object.is 來優化比較的速度。也就是說,若是你把對象或數組的新實例做爲 props 傳入,比較時該算法會返回 false,並從新計算備忘的值。

傳入備忘的 props

在本例中,未經使用 React.memo 的過濾器組件須要優化。這曾是父組件設置 props 的方式:

function handleChange(value => { ... }); <RangeSlider value={[minValue, maxValue]}  
  onChange={handleChange}
/>
複製代碼

每渲染一次,都要建立 handleChange 的一個實例,並傳入一個新的數組實例做爲 props。這就致使 RangeSlider 組件老是更新,儘管有 React.memo 包裹,由於 Object.is() 比較算法老是返回 false。爲了精確優化,我得用下列代碼重構:

const handleChange = useCallback((value) => {
    ...
}, []);

const value = useMemo(() => [minValue, maxValue], [minValue, maxValue]);

<RangeSlider value={value} onChange={handleChange} /> 複製代碼

若是依賴數組爲空,那麼 handleChange 則僅在掛載時更新。不管 minValuemaxValue 什麼時候更改,value 總會返回一個新數組。

我對 Histogram 組件作了一樣的優化,Histogram 組件把 props 傳到 HistogramBuckets 子組件中。

小提示:要想快速找出兩次渲染中哪些 props 發生了變化,能夠用這個精巧的 hooks:useWhyDidYouUpdate

成果

經過方便快捷的優化,組件的性能獲得了顯著提高。通過備忘優化後,在相同的操做下,Histogram 組件的渲染時間縮短到了 0.5 毫秒。比起原來的 72.7 毫秒加上第二個直方圖消耗的 402.8 毫秒,這但是超過千倍的提速啊!🤩 最終成果就是,僅用了極小的努力,就得到了更流暢的用戶體驗。

備忘優化後的直方圖渲染時間


加入 C°F

另外,若是你被本文驚豔到了,CLEVER°FRANKE 的大門永遠爲達人敞開哦。來咱們的招聘傳送門看看,若是感興趣,歡迎向咱們展現你的超能力。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索