一篇帶給你 React.memo 如何使用?
大家好,我是前端西瓜哥。
最近做的新功能有性能問題,所以我想嘗試優(yōu)化一下 React 組件的性能。下面我們來好好學(xué)習(xí)一下 React.memo 的用法。
組件狀態(tài)更新和重渲染
當(dāng)某個(gè)組件里的狀態(tài)發(fā)生改變時(shí),React 會(huì)調(diào)用該組件的 render 方法,生成新的 React 元素樹,和原來的虛擬 DOM 對(duì)比,找出不同的地方然后給真實(shí)的 DOM 打補(bǔ)丁。
如果有子組件,它也會(huì)被重新渲染。
這里有一個(gè)問題:有些子組件其實(shí)并沒有發(fā)生狀態(tài)改變,也被重新渲染,占用了 CPU 資源。
雖然這個(gè)操作是不必要的,但 React 并不能知道傳給子組件的 props 是否發(fā)生了改變。
但 React 提供了 React.memo() 方法,希望你能通過它來告知 React 該組件是否跳過重渲染。
React.memo
React.memo 是一個(gè) 高階組件。
所謂高階組件,其實(shí)指的就是一個(gè)接收組件并返回一個(gè)組件的函數(shù)。起名參考了高階函數(shù),但確實(shí)花里胡哨了一些。
React.memo 的作用是 緩存 組件,它會(huì)對(duì)傳入的組件加上緩存功能生成一個(gè)新組件,然后返回這個(gè)新組件。
在傳給組件的 props 的屬性和值沒有發(fā)生改變的情況下,它會(huì)使用最近一次緩存的結(jié)果,而不會(huì)進(jìn)行重新的渲染,實(shí)現(xiàn)跳過組件渲染的效果。
下面是一個(gè)示例:
const Comp = ({ color }) => { return ( <div> <div>{color}</div> </div> );};// 生成一個(gè)可以緩存結(jié)果的組件const MemoriedComp = React.memo(Comp);
當(dāng)我們使用 MemoriedBox 時(shí),如果傳入的 props.color 保持不變的話,MemoriedBox 組件就不會(huì)發(fā)生重渲染。
這里有一個(gè)演示例子:
https://codesandbox.io/s/react-memo-huan-cun-ce-shi-k96vd4。
保證 props 的有效對(duì)比
比較算法
React.memo 判斷是否使用緩存,默認(rèn)使用的是淺比較,也就是只比較第一層的 key。
shallowEqual 主要是通過 Object.is 來對(duì)比。源碼實(shí)現(xiàn)地址如下:
https://github.com/facebook/react/blob/HEAD/packages/shared/shallowEqual.js。
有時(shí)候我們希望自定義比較方法,這時(shí)候我們可以使用 React.memo 方法的第二個(gè)參數(shù),傳入我們自定義的比較方法。
const isEqual(prevProps, nextProps) { // 自定義對(duì)比方法}const MemoriedComp = React.memo(Comp, isEqual);
自定義比較方法會(huì)拿到兩參數(shù):舊的和新的 props 對(duì)象,然后根據(jù)該方法的返回值來決定是否使用緩存。如果為真值,使用緩存,否則重新渲染并把新的渲染結(jié)果緩存下來。
假設(shè)我們有一個(gè) prop 是數(shù)組,但因?yàn)槁暶髡Z句寫在組件里,所以每次渲染時(shí)都會(huì)生成一個(gè)指向新的內(nèi)存空間的另一個(gè)數(shù)組,導(dǎo)致新舊 prop 指向不同的內(nèi)存對(duì)象,但它們的數(shù)組元素卻是依次相等。
[1, 2] === [1, 2] // false
有時(shí)候我們想將它們認(rèn)為 “相同”,能夠觸發(fā) React.memo 方法的緩存。
用淺比較會(huì)返回 false,進(jìn)行重渲染,不符合我們的預(yù)期。這時(shí)候就可以使用自己實(shí)現(xiàn)的 深比較(深度遞歸比較),可以考慮使用比較有名的 lodash.isEqual。
緩存函數(shù)
對(duì)于數(shù)組和普通對(duì)象,我們可以用深比較來判斷 “相等”。但對(duì)于函數(shù)組件中每次都會(huì)被重新構(gòu)建的函數(shù),顯然是行不通的。函數(shù)沒有結(jié)構(gòu)。
對(duì)于函數(shù),我們可以使用 useCallback。
const Comp = () => { const onClick = React.useCallback(() => { if (isOk) sumbit(); }, [isOk]) return ( <div> <Button onClick={onClick}></Button> </div> )}
useCallback 接受一個(gè)函數(shù)和一個(gè)依賴項(xiàng)數(shù)組,當(dāng)依賴項(xiàng)數(shù)組里的元素沒有改變時(shí),會(huì)使用最后一次緩存的函數(shù),否則會(huì)使用傳入的新函數(shù)。
除了用 useCallback 緩存函數(shù),我們還可以用 useMemo 來緩存其他的對(duì)象值。
避免負(fù)優(yōu)化
- 只渲染一次,之后都不會(huì)更新的組件,不要使用 React.memo。
- props 每次都會(huì)改變的組件,不要使用 React.memo,使用 React.memo 只會(huì)帶來不必要的新舊 props 比較和無意義的緩存。
- 組件如果很簡(jiǎn)單,不建議使用 React.memo,并不能帶來多大提升,而使用 React.memo 本身就有心智負(fù)擔(dān)。
- 如果你無法很好地量化性能,不建議使用 React.memo。
結(jié)尾
React.memo 可以幫助我們跳過一些不必要的組件渲染。但要維護(hù)好對(duì)象類型 prop 的不改變,確實(shí)對(duì)我們?cè)斐刹簧俚男闹秦?fù)擔(dān)。
React.memo 并不是一定會(huì)有正收益的,因?yàn)榫彺嬉彩怯谐杀镜摹?/span>