React 性能優化那件大事,使用 memo、useCallback、useMemo

你今天優化了嗎 ?

Leo Chiu
手寫筆記
6 min readNov 14, 2019

--

前言

在寫網頁時,我們通常習慣把一個頁面切割成很多的元件 (Component) ,讓我們容易組織與管理頁面的組成。但是在 React 中複雜的元件關係,如果沒有經過優化,將有可能會造成性能上的問題。

在 Function Component 中,重新渲染 (re-render) 很輕易就會被觸發,少量的元件時還不會發生太大的問題,但是,如果遇到大型的網站平台,大量的元件不斷地被重新渲染,將會給瀏覽器重大的負擔,會造成使用者體驗不佳。

以下將介紹 memouseMemouseCallback 這三種方法,這三種方法都是 React 提供用來減少不必要的元件重新渲染所造成的問題。

React.memo

我們經常會讓子元件依賴於父元件的狀態 (state) 或事件 (event),在父元件中宣告狀態與事件方法,並利用 props 將兩者傳遞到子元件中。

如果父元件的狀態被改變了,但是 props 的結果沒有變,子元件仍然會被重新渲染。可是子元件的結果根本沒有改變,多餘的渲染造成性能上的浪費。

所以,React 提供了 React.memo 來幫助我們解決這個的問題:

React.memo 是以 HOC (higher order component) 的方法使用,我們只要在需要減少渲染的元件外面再包一層 React.memo ,就可以讓 React 幫我們記住原本的 props。

Example

在範例中,可以改變父元件中的 input 綁定的狀態觸發重新渲染,可以看到使用 React.memo 的子元件在 props 都沒有改變的情況下不會觸發渲染,不會讓 refCount 的值遞增;反之,沒有使用 React.memo 的子元件在父元件渲染時,每次都被強迫重新渲染,refCount 不斷遞增。

然而,React.memo 是用 shallowly compare 的方法確認 props 的值是否一樣, shallowly compare 在 props 是 Number 或 String 比較的是數值,當 props 是 Object 時,比較的是記憶體位置 (reference)。

因此,當父元件重新渲染時,在父元件宣告的 Object 都會被重新分配記憶體位址,所以想要利用 React.memo 防止重新渲染就會失效。

要解決這個問題的方法有兩種,第一種是 React.memo 提供了第二個參數,讓我們可以自訂比較 props 的方法,讓 Object 不再只是比較記憶體位置。

第二種方法則是 React.useCallback,讓 React 可以自動記住 Object 的記憶體位址,解決 shallowly compare 的比較問題。

接下來,我們就來看看 React.useCallback

React.useCallback

當父元件傳遞的 props 是 Object 時,父元件的狀態被改變觸發重新渲染,Object 的記憶體位址也會被重新分配React.memo 會用 shallowly compare 比較 props 中 Object 的記憶體位址,這個比較方式會讓子元件被重新渲染。

因此,React 提供了 React.useCallback 這個方法讓 React 在元件重新渲染時,如果 dependencies array 中的值在沒有被修改的情況下,它會幫我們記住 Object,防止 Object 被重新分配記憶體位址。

所以,當 React.useCallback 能夠記住 Object 的記憶體位址,就可以避免父元件重新渲染後,Object 被重新分配記憶體位址,造成 React.memo 的 shallowly compare 發現傳遞的 Object 記憶體位址不同。

Example

同樣地,當我們在父元件改變 input 綁定的狀態觸發重新渲染,使用 useCallback 記住記憶體位址的 function 就沒有讓子元件重新渲染,也沒有讓 refCount 遞增;反之,沒有被記住記憶體位址的 function 都使得子元件被父元件強迫重新渲染。

另外,React 在官方文件中有提到, every value referenced inside the callback should also appear in the dependencies array,因此在使用 useCallback 時必須注意 dependencies array 中是否都包含了在 useCallback 中使用的變數,否則可能會讓 useCallback 失效。

詳情請見 🔗如何錯誤地使用 React hooks useCallback 來保存相同的 function instance

React.useMemo

有一種情況是與父元件無關,但是在重新渲染時,在元件中的 function 被重新呼叫,複雜的程式邏輯被重複執行了一件,將會造成重大的負擔,因此勢必要有辦法解決這個問題。

React.useMemo 讓 React 記住 function 的回傳值,如果 dependencies array 中的變數都沒有被經過修改,React.useMemo 將會沿用上次的回傳值。

Example

同樣地,當我們在父元件改變 input 綁定的狀態觸發重新渲染,使用 useMemo 記住回傳值的子元件就沒有重新渲染,讓 refCount 遞增;反之,沒有被記住回傳值的子元件都會被父元件強迫重新渲染。

React 官方特別提醒 You may rely on useMemo as a performance optimization, not as a semantic guarantee. 因此,不要什麼東西都丟到 useMemo 裡面,在需要優化效能時才引用,否則只是讓 React 處理更多事情,造成更大的負擔。

總結

memouseCallback 是經常被用來作為組合技的方法,memo 能夠偵測 props 有沒有修改,減少元件不必要的渲染;useCallback 讓 props 的 Object 能夠在父元件重新渲染時,不重新分配記憶體位址,讓 memo 不會因為重新分配記憶體位址造成渲染。

useMemo 的用法則是無關於父元件,主要用在當元件重新渲染時,減少在元件中複雜的程式重複執行。

分享就到這邊,如果喜歡我的文章可以幫我拍個幾下手,在閱讀文章時如果有遇到什麼問題,或是有什麼建議,都歡迎留言告訴我,謝謝。 😃

Reference

--

--

Leo Chiu
手寫筆記

每天進步一點點,在終點遇見更好的自己。 Instragram 小帳:@leo.web.dev