【React】useCallbackとuseMemoによる表示パフォーマンス改善

以前、`React.memo`によるReactの表示パフォーマンスの改善についてまとめました。
それに引き続き、今回は`React.memo`とあわせて使われるHooksである、`useCallback`と`useMemo`について調べ、レンダリングの挙動について検証しました。

https://qiita.com/suzuki0430/items/445ac1b38fa7ef383864

#`useCallback`
`useCallback`はメモ化されたコールバック関数を返すReact Hooksです。
インラインのコールバック関数とそれが依存している値の配列を渡すと、`useCallback`はそのコールバック関数をメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。

`useCallback`は主に親コンポーネントで使用し、メモ化されたコールバック関数は子コンポーネントのpropsとして渡します。
子コンポーネントを`React.memo`でエクスポートしていれば、propsの変化がない限り余計なレンダリングは起こりません。
このようにして、Reactの表示パフォーマンスを向上させてくれます。

##検証
Log nameを押すとconsole.logに文言が出力され、Increase Count1(2)を押すとカウントが表示されるような画面をつくりました。
レンダリング時に`functions`へ`logName`関数が追加されるようにし、`useCallback`の使用有無でその挙動がどのように変わるのかを調べました。
![スクリーンショット 2021-05-11 21.42.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/a99ea999-5bba-c8f9-7a87-c0bc6b002ae2.png)

###useCallbackを使わない場合
画面の実装は以下のようになります。

```jsx:App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

const functions = new Set();

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const incrementCount1 = () => setCount1(count1 + 1);
  const incrementCount2 = () => setCount2(count2 + 1);

  const logName = () => console.log('Yihua');

  functions.add(logName); //レンダリングの度にfunctionsインスタンスにlogName関数が追加される

  console.log(functions);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        Count1: {count1}
        <button onClick={incrementCount1}>Increase Count1</button>
        Count2: {count2}
        <button onClick={incrementCount2}>Increase Count2</button>
        <button onClick={logName}>Log name</button>
      </header>
    </div>
  );
};

export default App;
```

Increase Count1(もしくは2)を押すとState(count1, count2)の変化が起こるため、カウントが増える度に再レンダリングされます。
その結果、レンダリングの度に`functions`には`logName`関数が追加されています。
![スクリーンショット 2021-05-11 21.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/df511fe9-f06c-4828-4e0b-6f2d1b6da2a6.png)

###useCallbackを使う場合
`const logName = () => console.log('Yihua');`と記述していた箇所を`useCallback`で書き換えます。
このとき、第2引数には空配列を与えているので、初期表示でメモ化を行った後に関数の再生成は起こりません。

```jsx:App.js
  const logName = useCallback(() => console.log('Yihua'), []);
```

その結果、Increase Count1(2)を何回押しても、`functions`には`logName`関数は追加されていきません(Set(1)じゃなかった理由は調査中)
![スクリーンショット 2021-05-11 21.58.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/9291caef-0f33-73d2-4ca4-65dce51b3b04.png)

#`useMemo`
`useCallback`ではコールバック関数のメモ化を行いましたが、`useMemo`は値のメモ化を行います。

“作成用” 関数とそれが依存する値の配列を渡し、依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します。
この最適化によりレンダリングごとにコストの高い計算が実行されるのを避けることができます。

`useMemo`も`useCallback`と同様に、`React.memo`とあわせて使われます。

##検証
Increase Count1を押したときだけ`doSomethingComplicated`関数が実行され、計算結果がcomplexValueとして表示されるような画面を実装しました。
![スクリーンショット 2021-05-11 22.26.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/ca75c809-d26d-2baa-dc87-8ca1fc511000.png)

###`useMemo`を使わない場合
`doSomething`関数を追加して、`complexValue: {doSomethingComplicated()}`として表示します。

```jsx:App.js
  //レンダリングの度に実行される
  const doSomethingComplicated = () => {
    console.log('I am computing something complex');
    return ((count1 * 1000) % 12.4) * 51000 - 4000;
  };

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        Count1: {count1}
        <button onClick={incrementCount1}>Increase Count1</button>
        Count2: {count2}
        <button onClick={incrementCount2}>Increase Count2</button>
        complexValue: {doSomethingComplicated()}
        <button onClick={logName}>Log name</button>
      </header>
    </div>
  );
```

するとレンダリングのたび(Increase Count1だけではなくIncrease Count2を押しても)に`doSomethingComplicated`内のconsole.logが実行されます。
![スクリーンショット 2021-05-11 22.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/30dce776-ee84-2f2e-75e9-14e584363a71.png)

###`useMemo`を使う場合
`doSomethingComplicated`を`useMemo`で書き換えると以下のようになります。
第2引数に与えたcount1の値が変化したときだけ新しい値を返すようになります。

```js
  const doSomethingComplicated = useMemo(() => {
    console.log('I am computing something complex');
    return ((count1 * 1000) % 12.4) * 51000 - 4000;
  }, [count1]);
```

また、`doSomethingCompleted`は値を返すようになるので、以下の箇所も書き換えます。

```jsx
complexValue: {doSomethingComplicated}
```

こうすることで、Increase Count2をいくら押しても`doSomethingCompleted`は実行されないようになります。

#参考資料

https://ja.reactjs.org/docs/hooks-reference.html

https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2

https://www.udemy.com/share/101WH4CEEZd11VRno=/

https://qiita.com/official-events/b9ad63394fa2635dfca9