天天看點

10個小技巧幫助你提高React界面性能

10個小技巧幫助你提高React界面性能

作者:Matthew Tyson

衆所周知,沒有人會喜歡響應緩慢的Web界面。而作為最受歡迎的JavaScript架構之一,react提供了多種提高UI性能的方法。下面讓我們一探究竟吧。

總的說來,React是通過維護視圖中的記憶體(in-memory)模型來運作的。這通常被稱為虛拟DOM,它可以被用來确定實際DOM何時需要被更新。

不過,由于操控實際DOM的成本較高,是以我們需要確定僅在必要時才去更新DOM,進而提高整體性能。

為了從React架構中擷取最高性能,并提升React界面的響應效率,本文将從各種功能函數(如Suspense)、以及基于類的元件出發,和您讨論十項常用的、面向DOM的技術與方法。

shouldComponentUpdate

在編寫基于類的元件時,您可以重寫shouldComponentUpdate()的生命周期方法。該方法的目的在于:明确地聲明目标元件是否需要被重新渲染(re-rendering)。

值得注意的是,在更新實際DOM的生命周期中,渲染的開銷是非常巨大的。

是以,隻有在元件的屬性(props)或狀态(states)發生變化時,我們才需要讓React執行渲染。

有時您甚至可以跳過渲染,以避免整體調用所産生的開銷。

shouldComponentUpdate的簽名和操作比較簡單。在如下簡單示例中,元件需要知曉應該在何種指定觸發條件下,去執行更新。

該方法将接收到的屬性與狀态當作參數,如果傳回為true,元件将執行渲染,否則并不觸發渲染。

shouldComponentUpdate(nextProps, nextState) { 
    if (this.props.significant !== nextProps.significant) { 
      return true; 
    } 
    return false; 
  }      

雖然上述代碼段主要檢查的是屬性,但是它對于狀态也同樣适用。當然,在實際應用中,對于屬性或狀态的檢查,并判定是否傳回true,可能會更加複雜。

如果您需要比較某個簡單的淺層值(shallow value),那麼請使用下一個技巧--PureComponent。

PureComponent

如果您的元件僅需要對屬性和狀态進行簡單的淺層比較(shallow comparison,https://stackoverflow.com/a/5703797/467240),以确定是否需要渲染,那麼完全可以使用PureComponent之類的擴充基類--class MyComponent extends React.PureComponent。

它可以實作:當通過淺層比較,并未發現屬性或狀态發生了任何變化時,render()就不會被調用。

顧名思義,PureComponent表示:僅在屬性或狀态改變時,才會觸發輸出的更改,是以該元件是純淨的,不會帶有任何副作用。

useEffect

前面的技巧僅适用于那些基于類的元件。為了達到與正常功能性元件相似的效果,您可以使用useEffect hook和memo之類的功能性元件。

其中,useEffect與shouldComponentUpdate有着相似的效果,它允許使用者指定:僅在某些變量發生更改的情況下,生效某種特定的功能 ,進而避免了整體變更的開銷。下面是一個簡單的useEffect示例:

const MyComponent = (props) => { 
  useEffect(() => { 
    console.info("Update Complete: " + props.significantvariable); 
  }, [props.significantvariable]); 
}      

由上述代碼段可知,如果props.significantVariable已被更改(即變量發生了變化),那麼該代碼就會運作生效。

用React.memo提供記憶

作為一個高階元件,memo包裝了各種元件,并擴充了它們的行為能力。也就是說,如果功能性元件具有相同的屬性,memo便能夠以緩沖的方式,“記住”它們的結果。

據此,它可以有效地防止功能性元件在無視屬性是否一緻的情況下,去盲目地執行渲染。

為了模仿PureComponent隻關注屬性的行為,我們可以使用如下代碼段來包裝某些功能性元件,使其隻檢查屬性的更改,而非狀态。

由于屬性和狀态是不同的,是以通過比較,一旦props.quote被認定為未發生改變,則其對應的元件也不會重新渲染。

const MyComponent = (props) => { 
  return <span>props.quote</span> 
} 
export default React.memo(SomeComponent)      

同時,React.memo 可以通過第二個參數,來檢查函數的等效性:

export default React.memo(MyComponent, (oldProps, newProps) => {} );      

通過上述代碼,我們可以實作對用例新的和舊的屬性進行比較。如果屬性相等,該函數則傳回true。

值得注意的是,這與我們在前面介紹的shouldComponentUpdate,在發現元件出現更新時傳回true,正好相反。

視窗化(清單虛拟化)

現在,讓我們将注意力轉移到一項同時适用于功能性和類元件的技術--視窗化(windowing)上。

例如有一個具有數千行記錄的資料表或清單,如果您想在該表所對應的應用界面上顯示大量資料集,那麼就需要采用“視窗化”的方式來查詢資料。

也就是說,我們可以通過一次性僅加載和顯示部分資料的形式,防止大量資料“卡死”應用的使用者界面(UI)。

為此,我們時常可以用到react-window庫(請參見--https://github.com/bvaughn/react-window)。

函數緩存

如果您覺得函數調用的成本過高,那麼可以考慮對其進行緩存。

如果各個參數相同,而且緩存能夠傳回結果,我們就可以使用存儲式緩存(memorized cache)的方式,來避免各種針對資料擷取的調用。

當然,函數緩存是否真的适用,還取決于函數的具體特征。

延遲加載和代碼拆分

所謂延遲加載是指:我們僅在必要時,才去加載資料。React 16.6引入了React.lazy(),它允許使用者對代碼按需進行拆分。

這意味着,您可以在使用正常元件文法的同時,獲得各種延遲加載的語義。

當然,React 16.6之前的版本,并非無法實作代碼拆分,隻是在處置大型代碼庫時,相對比較繁瑣。

并發模式、Suspense和useDeferredValue

作為React 16的一項最顯著的新功能, 并發模式 可以讓使用者通過使用Suspense元件,實作資料擷取和渲染的并行處理,進而極大地提高應用程式的實際感覺性能。

我們除了能夠用Suspense元件來定義資料的擷取區域之外,還可以使用諸如useDeferredValue等由React 16帶來的新元件,來提升自動建議(auto-suggest)等工作方式,進而避免使用者碰到諸如錯誤性的鍵入等不良的體驗。

資料擷取的防抖(Debounce)和限流(throttle)

大多數情況下,我們可以通過debounce或throttle函數,來更好地處理React的并發模式。

如果您的代碼庫被鎖定為舊版的渲染引擎,而無法開啟并發模式時,此類函數便可以有效地避免在資料擷取的過程中,出現混亂的局面。

例如,如果您想在使用者鍵入資料的同時,實時地擷取他們的輸入,那麼由于每個擊鍵都會觸發一個請求,是以整體的性能會大打折扣。

對此,我們便可以使用debounce或throttle函數,來緩解此類問題。

分析(Profiling)

除了上面提到的技術,我們還可以通過對應用程式進行性能分析,來獲悉性能瓶頸的所在,并驗證上述改進方法的實際效果。

目前,像Chrome和Firefox之類的浏覽器,都帶有内置的分析器(profiler)。一旦啟用了React的開發模式(dev mode),您将可以通過分析器,來檢視某些正在使用的特定元件。

這對于檢查網絡的狀态,以及識别後端調用的延遲,都是非常實用的。據此,您可以清晰地判斷出,到底是前端JavaScript的代碼問題,還是存在着需要後端修複的缺陷。

此外,React 16.5以及更高的版本,還提供了一個名為DevTools Profiler(https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html)的工具。

它既能夠為處于并發模式的各個函數,提供了更加詳盡的服務功能與內建;又可以通過多種方法,對應用程式的行為活動進行切片(slice)和切塊(dice)。

另一類 Profiler元件 則能夠展現元件渲染生命周期中的各種詳細資訊。

React的生産環境建構

最後,在部署生産環境時,您還需要確定生産環境建構(production build)的精簡性、且不包含任何開發調試過程中的日志記錄。