Try   HackMD

React 優化項目(三):useMemo

tags: React Optimize Hooks useMemo OptimizeRender

React 優化項目系列,減少大量且不必要的元件重新渲染週期

React 優化項目(一):React.memo

如何觸發更新的
紀錄 props value

React 優化項目(二):useCallback

紀錄 func 記憶體位置

React 優化項目(三):useMemo

紀錄 object value 避免重複執行相關處理


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
不會的,在同一個 block 裡,如果沒有穿插其他非同步項目,React 會將這兩個 state 在同一個 re-executed 行程裡一起做更新。

...
const navHandle = (navPath) => {
  // 是不是這裡就會 re-executed 兩次?
  setCurrentNav(navPath)
  setDrawerIsOpen(false)
};
...
  

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


範例測試

再開發過程中,常常需要對 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 也再執行了一次。

這是因為 javascript Object 是傳址的特性,所以對 React.memo 來說,這個 object 是不一樣的

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

使用 React.useMemo()

紀錄 object value 避免重複執行相關處理

可以先了解基本用法 >>

父層元件 RenderTry 修改

...
    // 將原本傳進去的 items 包裹在 useMemo 裡
    const items = useMemo(() => [5, 3, 1, 10, 9], []);
...
    <DemoList items={items} />

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

可以看到 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]);

這麼一來當我的 DemoList 外層 props items 未曾改變,但再做其他更新的事情時,就可以避面 sortList 重新做計算的動作!