前言
在寫網頁時,我們通常習慣把一個頁面切割成很多的元件 (Component) ,讓我們容易組織與管理頁面的組成。但是在 React 中複雜的元件關係,如果沒有經過優化,將有可能會造成性能上的問題。
在 Function Component 中,重新渲染 (re-render) 很輕易就會被觸發,少量的元件時還不會發生太大的問題,但是,如果遇到大型的網站平台,大量的元件不斷地被重新渲染,將會給瀏覽器重大的負擔,會造成使用者體驗不佳。
以下將介紹 memo
、 useMemo
、 useCallback
這三種方法,這三種方法都是 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 處理更多事情,造成更大的負擔。
總結
memo
與 useCallback
是經常被用來作為組合技的方法,memo
能夠偵測 props 有沒有修改,減少元件不必要的渲染;useCallback
讓 props 的 Object 能夠在父元件重新渲染時,不重新分配記憶體位址,讓 memo
不會因為重新分配記憶體位址造成渲染。
useMemo
的用法則是無關於父元件,主要用在當元件重新渲染時,減少在元件中複雜的程式重複執行。
分享就到這邊,如果喜歡我的文章可以幫我拍個幾下手,在閱讀文章時如果有遇到什麼問題,或是有什麼建議,都歡迎留言告訴我,謝謝。 😃