# React 優化項目(三):useMemo ###### tags: `React` `Optimize` `Hooks` `useMemo` `OptimizeRender` React 優化項目系列,減少大量且不必要的元件重新渲染週期 #### [React 優化項目(一):React.memo](https://hackmd.io/@yellow/optimize-render) > 如何觸發更新的 <br />紀錄 props value #### [React 優化項目(二):useCallback](https://hackmd.io/@yellow/optimize-render-usecallback) > 紀錄 func 記憶體位置 #### [React 優化項目(三):useMemo](https://hackmd.io/@yellow/optimizer-render-useMemo) > 紀錄 object value 避免重複執行相關處理 <br /> ## `State` 的更新流程以及批次更新 開始前先來了解 State 的更新流程,以及批次更新。 - State 的更新是不及時的 - 當 state 觸發更新執行 `re-executed`,state 才會是最新的。 ``` ... const [showParagraph, setShowParagraph] = useState(false); ... const toggleParagraph = useCallback(() => { allowToggle && setShowParagraph((pre) => !pre); // 假如 setShowParagraph 有執行, // 在這裡印出 showParagraph 一樣會是 false }, [allowToggle]); ... } ``` 那如果更新的 state 不止一個的時後,元件是不是就會一直跑 `re-executed` 像是下面範例,更新兩個 state,所以跑兩次 `re-executed`?<br /> 不會的,在同一個 block 裡,如果沒有穿插其他非同步項目,React 會將這兩個 state 在同一個 `re-executed` 行程裡一起做更新。 ``` ... const navHandle = (navPath) => { // 是不是這裡就會 re-executed 兩次? setCurrentNav(navPath) setDrawerIsOpen(false) }; ... ``` ![](https://i.imgur.com/XkcD0dt.png) <br /> ## 範例測試 > 再開發過程中,常常需要對 origin list 做一些處理,可能是 sort, format 或是對某欄位進行複雜運算,如果 origin list 沒有改變,是否我的處理也可以不需要再跑一次。 在 `RenderTry` 引入 `DemoList` ``` export default function RenderTry() { ... console.log('APP RUNNING'); const [listTitle, setListTitle] = useState('My Title'); const changeTitleHandle = useCallback(() => { setListTitle('New Title!'); }, []); ... <h2>{listTitle}</h2> // 傳進 items <DemoList items={[5, 3, 1, 10, 9]} /> <Button onClick={changeTitleHandle}>changeTitle</Button> ... ``` #### 子元件 `DemoList` ``` import React from 'react'; function DemoList({ items }) { console.log('DEMO LIST RUNNING!'); const sortList = items.sort((a, b) => a - b); return ( <> <ul> {sortList.map((item) => ( <li key={item}>{item}</li> ))} </ul> </> ); } // 加上 React.memo export default React.memo(DemoList); ``` #### 執行更新觀察 可以看到 重新整理執行一次更新,點選 btn 更新 state 時,`DemoList` 也再執行了一次。 :::info 這是因為 javascript Object 是傳址的特性,所以對 React.memo 來說,這個 object 是不一樣的 ::: ![](https://i.imgur.com/SzXO4p9.gif) ## 使用 React.useMemo() > 紀錄 object value 避免重複執行相關處理 [可以先了解基本用法 >> ](https://hackmd.io/@yellow/react-hooks#useMemo-%E8%A8%98%E6%86%B6%E5%80%BC) #### 父層元件 RenderTry 修改 ``` ... // 將原本傳進去的 items 包裹在 useMemo 裡 const items = useMemo(() => [5, 3, 1, 10, 9], []); ... <DemoList items={items} /> ``` ![](https://i.imgur.com/sEeA04C.png) 可以看到 DemoList 就不再重複執行了。 但 useMemo 主要用在當前元件重新渲染時,通過記憶職來避免重複執行,他是無關父元件的。 所以較正確使用,應該是將 `useMemo` 加在 `DemoList 裡面` #### 修改 DemoList 元件 ``` import React, { useMemo } from 'react'; function DemoList({ items }) { // 使用 useMemo 儲存記憶值 const sortList = useMemo(() => { console.log('sortList!'); return items.sort((a, b) => a - b); }, [items]); ``` :::info 這麼一來當我的 `DemoList` 外層 props items 未曾改變,但再做其他更新的事情時,就可以避面 sortList 重新做計算的動作! :::