雖然說之前已經認識了 useCallback、useMemo,但有時還是對於什麼時候要用它們的時機有些模糊,所以就透過這篇文章釐清觀念。
首先要知道一點是如果沒有必要可以不用去用它們。盲目的使用反而更耗效能,因為它們在 React 底層記憶值也是需要消耗效能的,同時程式碼也會變得更複雜。
在此先複習一下 Referential Equality,對於理解後面的內容會有所幫助。
我們知道陣列、物件、函式等物件型別擁有 call by reference 的特性,所以即使有兩個物件它們裡面的元素、屬性、函式內容都一樣,在做 === 比較時仍然會是 false。
const hero1 = {
  name: 'Batman'
};
const hero2 = {
  name: 'Batman'
};
console.log(hero1 === hero1); // => true
console.log(hero1 === hero2); // => false
這樣跟 React 又有什麼關係?讓我們繼續看下去!
在下面的範例可以看到一個被 React.memo() 包覆的元件 Child,而且接收到一個函式 onClick 當作 props。
父元件重新渲染時,雖然都還是同一個函式,但函式被重新產生了,所以 React.memo() 會以為傳入的 props 改變而重新渲染 Child 元件,所以加上了 useCallback。
function Parent() {
  const [count, setCount] = useState(0);
  const onClick = useCallback(() => {
    console.log('click');
  }, []);
  return (
    <>
      <Foo onClick={onClick} />
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>count increment</button>
    </>
  );
}
const Child = memo({ onClick }) => {
  // ...略
  return (
    <>
     // ...
     <button onClick={onClick}>可點擊</button>
    </>
  )
});
useEffect 的 dependency array 的其中一個元素為物件型別時,若物件重新產生就會被 useEffect 當作 dependency array 改變,像以下的例子就造成了 useEffect 無限 render,所以也需要加上 useCallback。
跟此範例類似的例子在 pjchender 大大的 從 Hooks 開始,讓你的網頁 React 起來 Day20 篇 也有提到,有興趣的讀者可以跳到"在 useEffect 的 dependencies 中放入函式 - useCallback 的使用"的段落去閱讀。
const App = () => {
  const [id, setId] = useState(1);
  const [detail, setDetail] = useState("");
  const getDetail = useCallback(() => {
    fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
    .then(res => res.json())
    .then(json => {
      setDetail(json); // state updated
    }));
  }, [id]);
  useEffect(() => {
    getDetail();
  }, [getDetail]);
  return (
    <>
      <p>{detail.name}</p>
      <p>Current id: {id}</p>
      <button onClick={() => setId(id + 1)}>id increment</button>
    </>
  );
}
Referential Equality 的概念也可以套用在 useMemo,所以也可以留意一下 useEffect 的 dependency array。
const App = ({ param1, param2 }) => {
  const params = useMemo(() => {
    return { param1, param2, param3: 1 };
  }, [param1, param2]);
  useEffect(() => {
    callApi(params);
  }, [params]);
}
例如 filter 很多元素的陣列、複雜數學運算
How To Use Memoization To Drastically Increase React Performance
When to useMemo and useCallback
如何錯誤地使用 React hooks useCallback 來保存相同的 function instance
蠻好奇使用 useMemo 或是 useCallback Hooks 後
整個page是否能真的提升多少效能
不錯的問題!之前看 useMemo 或是 useCallback 的教學大多也是教你怎麼使用 or 何時用,到是還沒真的看過使用前後的效能比較