React手冊 Hooks 之 useMemo
描述
????React 官網(wǎng)對 useMemo?的描述原文
????
useMemo
is a React Hook that lets you cache the result of a calculation between re-renders.useMemo 是一個(gè) React Hook 可以在你重新渲染之間緩存一個(gè)計(jì)算結(jié)果.
????可以看出 useMemo 和 useCallback 非常類似, 都是起到緩存優(yōu)化的作用,?區(qū)別在于 useMemo 用來緩存計(jì)算結(jié)果, 而 useCallback 用來緩存函數(shù)定義, 不過這只是 React 官方給出的解釋, 如果你看過這兩個(gè)方法的源碼, 你會發(fā)現(xiàn)其實(shí)這兩個(gè)方法也是可以混用的, useMemo 也可以緩存函數(shù), 而 useCallback 也可以用來緩存變量或計(jì)算結(jié)果, 只不過這不符合 React 的預(yù)期(主要因?yàn)?React 認(rèn)為一個(gè)匿名函數(shù)返回另一個(gè)匿名函數(shù)的定義看起來很笨重[確實(shí)]), 對后續(xù)的 React 更新并不友好, 所以 React 提出了一個(gè)專門針對函數(shù)的 useCallback.
場景
????useMemo?是為了解決?React.memo?包裝組件之后, 每次渲染父組件時(shí), 給子組件傳遞的變量始終是全新的對象, 導(dǎo)致 React.memo 優(yōu)化失效的問題, 它會在父組件外部緩存變量, 當(dāng)父組件渲染時(shí), 會返回緩存的變量, 讓 React.memo 能拿到相同的變量引用, 從而讓優(yōu)化生效.
????useMemo 和 useCallback 一樣, 應(yīng)該只用做性能優(yōu)化, 如果你的代碼沒有它之后無法正常工作, 那就應(yīng)該修復(fù)潛在問題, 然后用 useMemo 或 useCallback 提高性能.
????接口定義:
參數(shù)
calculateValue: Function
? ? 計(jì)算需要緩存的值的函數(shù), 應(yīng)該是純函數(shù)且不帶有參數(shù), 返回任意類型的值, React 會在初始渲染階段調(diào)用這個(gè)函數(shù), 并將結(jié)果緩存, 當(dāng) dependencies 發(fā)生變化時(shí), 會再次調(diào)用并更新緩存中的結(jié)果, 然后從 useMemo 返回.
dependencies: Array<mixed>
? ? calculateValue 函數(shù)中引用的所有觸發(fā)更新的值的數(shù)組集合, 能夠觸發(fā)的值包括?props, state?以及直接在組件主體內(nèi)聲明的所有變量和函數(shù). 這個(gè)數(shù)組的長度應(yīng)該是固定的, 每次渲染時(shí), React 會對數(shù)組內(nèi)的值進(jìn)行 Object.is 判斷, 如果發(fā)生變化則重新計(jì)算新值, 如果沒有變化, 則返回緩存中的值. 如果沒有傳遞該值, 那么會導(dǎo)致 useMemo 始終返回新值, 如果傳遞空數(shù)組[], 那么 useMemo 會始終緩存該值.
返回
????初始渲染時(shí), useMemo 會返回 calculateValue 函數(shù)的返回值, 后續(xù)渲染中它會先判斷依賴項(xiàng)是否發(fā)生變化, 如果變化就重新使用?calculateValue 計(jì)算新值并緩存, 如果沒有變化, 那么返回緩存中的值.
用法1
? ? 跳過昂貴的重新計(jì)算, 要在重新渲染之間緩存計(jì)算結(jié)果, 可以像下面這樣使用 useMemo
????當(dāng) keyword 和 time 沒有發(fā)生變化, 那么 useMemo 始終返回相同的結(jié)果, 這樣可以跳過不必要的計(jì)算, 如果 filterBigList 的計(jì)算量很大的是時(shí)候, 效果會非常明顯, 這樣的緩存被稱為記憶化(Memoization).
????如果只在首次渲染時(shí)計(jì)算, 并且沒有任何依賴項(xiàng)目,?代碼看起來像這樣: useMemo(() => { ... }, []); 也就是說計(jì)算之后希望能一直緩存結(jié)果, 那么可以使用 useState 或 useRef, 這種情況下可能 useMemo 并不是最優(yōu)的選擇, 原因是 React 官方給出的解釋, 在未來的 React 規(guī)劃中, 存在丟棄緩存的設(shè)計(jì), 緩存的目的是為了跳過多余的渲染, 在一些特定的, 一定不會渲染的情況時(shí), 緩存就是多余的, 如果你只是用 useMemo 來做優(yōu)化, 這并沒有什么影響, 但是如果擔(dān)心丟失緩存對業(yè)務(wù)有影響, 那么可以使用 useState 和 useRef 來保證緩存.
????
用法2
????跳過組件的重新渲染, 在默認(rèn)情況下, 一個(gè)組件重新渲染時(shí), React 會遞歸地重新渲染它的所有子組件, 這在大多數(shù)時(shí)候都是不必要的, 所以需要使用 React.memo 方法將組件包裝, 包裝后的組件, 在 props 沒有變化的時(shí)候, 不會再進(jìn)行重新渲染,
????在例子中, 使用 React.memo 包裝了子組件 Children 同時(shí) Children 接收一個(gè)?data={childrenData} 的 props, 此時(shí)會發(fā)現(xiàn) React.memo 并沒有生效, 每次 MyApp 重新渲染時(shí), 子組件 Children 依然會跟著渲染, 原因其實(shí)是 React.memo 的優(yōu)化只有在 props 沒有變化時(shí)才會生效, 而在例子中傳遞的 childrenData, 在每次重新渲染的時(shí)候, 都會獲得一個(gè)全新的對象引用, 這樣會導(dǎo)致 React.memo 對 props 進(jìn)行 Obejct.is 對比的時(shí)候, 始終不會像等, 這樣 React.memo 的優(yōu)化就會失效.
????所以需要使用下面這樣使用 useMemo 來讓 React.memo 優(yōu)化生效
用法3
????使用 useMemo 包裝 JSX 節(jié)點(diǎn), 因?yàn)?JSX 節(jié)點(diǎn)可以看作是一個(gè)類似 { type: "List",?props: { ... }?} 這樣的對象, 那么如果是對象就可以被 useMemo 緩存(記憶).
????這樣的用法實(shí)際上和使用 const ListMemo = React.memo(List); 是一樣的, 都是當(dāng) name 發(fā)生變化后重新渲染 List 組件, 區(qū)別在于 React.memo 可以指定一個(gè)判斷函數(shù)(React.memo 的第二個(gè)參數(shù)), 而不僅僅是根據(jù) props 進(jìn)行淺比較, 而 useMemo 只能控制傳遞進(jìn)去的?dependencies 相對而言沒有 React.memo 靈活, 所以更推薦使用 React.memo 直接包裝組件.
總結(jié)
useMemo 是一個(gè)可以將值記憶化(Memoization) 的一個(gè) hook;
useMemo 需要謹(jǐn)慎使用, 管理好 dependencies 是優(yōu)化的關(guān)鍵;
useMemo 不應(yīng)該用來實(shí)現(xiàn)功能, 只用來做優(yōu)化;
dependencies 不傳會始終返回新值, 傳空數(shù)組會在渲染一次之后一直緩存;
如果希望值在一次計(jì)算之后一直緩存,?沒有依賴項(xiàng)目, 考慮使用 useState 和 useRef 代替;
useMemo 和 useCallback 在實(shí)現(xiàn)上并沒有什么區(qū)別, 只是調(diào)用的方式不同;
雖然 useMemo 可以緩存 JSX 節(jié)點(diǎn), 但是不推薦, 優(yōu)先使用?React.memo 緩存組件.