React.memo / useCallback / useMemo を書きながら学ぶ（React Hooks 入門シリーズ 1/6）

## ロードマップ

React 16.8 で追加された機能であるReactの`Hooks`について書いてあります。

書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。

- [React.memo / useCallback / useMemo](https://qiita.com/ryosuketter/items/c503d98e16c28f516c4d) :point_left_tone2: 今回
- [Context / useContext](https://qiita.com/ryosuketter/items/90bd9edd5d91b8daf5c9)
- [useRef](https://qiita.com/ryosuketter/items/f2cc554cc89fb41d5a0b)
- [useState](https://qiita.com/ryosuketter/items/40dfc139ed90ebffd284)
- [useEffect](https://qiita.com/ryosuketter/items/6551ee0d75a3da003980)
- [useReducer](https://qiita.com/ryosuketter/items/3d59f1c503fe27fa0cb9)

## はじめに

今回は、Reactの組み込みフックである`useCallback`と`useMemo`の説明をします。

また、`useCallback`は、`React.memo`という Reactの API と併用するので、`React.memo`の解説もします。

## useCallback とは

一言で言うと、パフォーマンス向上のためのフックです。

具体的に言うと、コールバック関数をメモ化して、不要な関数インスタンスの作成を抑制します。

これによってパフォーマンスを向上させています（不要な関数インスタンスの作成が重い時は特に）。

ちなみに、メモ化とはプログラム高速化のための最適化の1つです。関数の定義や実行の結果を再利用するために一時的に保持しています。

依存配列の要素（deps）が変化した場合にのみメモ化した値を再計算します。

```js
const callback = useCallback(コールバック関数, [依存配列])
```

依存している値がなければ、空の配列OKです。

```js
const callback = useCallback(コールバック関数, [])
```

依存している値が更新されれば、関数が再生成されます。

例を1つ

```js
const callback = useCallback(() => console.log(count), [count])
```

上記の場合、`count`が更新されない限り、関数は再生成されません。

### サンプルコード

こんなん作るとします。

![スクリーンショット 2021-01-28 0.17.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/c4a406f6-b89d-4b04-cff1-a694c6e52529.png)

各ボタンクリック時の結果は、なんとなくわかりますよね？

![スクリーンショット 2021-01-28 0.49.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/6e0d0fee-6111-783b-a529-10bed20c4e2e.png)

今の状態だと、どのボタンを押しても、`Title`コンポーネントとか（再レンダリングしても結果同じなやつ）も含め、全てのコンポネントが再レンダリングされます。

`console.log`仕込んでるので、それがわかります。

![8de0ba44bab2230c72d3dddad8c8241a (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/07d8e790-9368-1b30-298e-a8ee15060808.gif)

<details><summary>サンプルコード</summary><div>

```js:App.js
import React, { useState } from "react";
import "./styles.css";
import Title from "./components/Title";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = () => setHeight(height + 1);
  const incrementWeight = () => setWeight(weight + 1);
  return (
    <>
      <Title />
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;
```

```js:components/Title.js
import React from "react";

const Title = () => {
  console.log("Title");
  return <h1>身長と体重の入力</h1>;
};

export default Title;
```

```js:components/Button.js
import React from "react";

const Button = ({ handleClick, children }) => {
  console.log(`Button - `, children);
  return <button onClick={handleClick}>{children}</button>;
};

export default Button;
```

```js:components/Count.js
import React from "react";

const Count = ({ text, count }) => {
  console.log(`Count - `, text);
  return (
    <p>
      {text} : {count}
    </p>
  );
};

export default Count;
```

</div></details>

### ☝️のサンプルコードの問題点

どのボタンを押しても、全てのコンポネントが再レンダリングされることが、パフォーマンスが悪いとしましょう。

そこで、パフォーマンス改善に役立つ`React.memo`と`useCallback`を試してみましょう。

#### そこで、React.memoの出番

React.memo とはコンポーネントをメモ化（計算結果を再利用するために保持）するReactのAPIです。

キャッシュみたいなもんです。

もう少し細かい言い方をすると、[HOC](https://ja.reactjs.org/docs/higher-order-components.html)で、React.memoを使うことで、propsの値が変わらないなら、関数コンポネントのレンダリングを抑制することができるAPIです。

なので、以下のようなコンポネントをメモ化するとパフォーマンス上有効です。

- レンダーコストが高いコンポネント
- 頻繁に再レンダーされるコンポネントの子コンポネント

やってみましょう。変更点は、赤枠のみ。👇

![musing-almeida-g5ogr_-_CodeSandbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/c4f5256c-6d07-2e0e-9dd4-879d5afff80c.png)

`身長+1`ボタンをクリックしたら...

以下のコンポネントが再レンダリングされます。

- 身長のCountコンポネント
- 身長のButtonコンポネント
- 体重のButtonコンポネント（👈 なんでこいつが再レンダリングされるんだ？　してほしくない）

以下のコンポネントは再レンダリングされません。

- 体重のCountコンポネント
- Titleのコンポネント

![musing-almeida-g5ogr_-_CodeSandbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/91934181-b39f-24c8-fbe2-3497f3957058.png)

##### なぜ「身長+1ボタン」をクリックしたら体重のButtonコンポネントがレンダリングされるのか？

App コンポネントが再レンダリングされるたびに、関数も再生成され、再生成の前後で関数は等価ではありません。

なので、`React.memo`で子コンポネントをメモ化しても **コールバック関数をpropsとして渡す場合は子コンポコンポネントは必ず再レンダリングされます。**

この問題を解決するのが`useCallback`です。

#### 次に、usecallbackの出番

変更点は、赤枠。👇

![musing-almeida-g5ogr_-_CodeSandbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/86419f1d-cd70-466a-8a01-dea9d2cea1b7.png)

実行結果

![399b7c1abfe524dedf2410cad071f1b0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/946ce59d-ca14-5abe-c6e3-d0a0863bd6c6.gif)

useCallbackを使うことでコンポネントの再レンダリングを最適化することができました。

このように、子コンポネントにコールバック関数をpropsとして渡す場合は使ってみるといいと思います。

#### useCallbackの注意点

前述の通り、`useCallback`は`React.memo`と併用するものなので、次のような使い方をしても再レンダリングをスキップできません

- `React.memo`でメモ化していないコンポネントに`useCallback`でメモ化したコールバック関数を渡すとき
- `useCallback`でメモ化したコールバック関数を、それを生成したコンポーネント自身で利用するとき

#### useCallbackの疑問点...依存関係の配列にsetState関数を含める必要があるか？

ない。

コード書いてると見かけるので、調べてみましたが、[公式ドキュメント](https://reactjs.org/docs/hooks-reference.html#basic-hooks)見ると不要だそうです。

> Note
>
> React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

訳
React は再レンダリングで`setState`関数の等価に保たれ、変化しないことを保証します。
従って `useEffect`や`useCallback`の依存リスト（依存配列）にはsetState関数を含めなくてもいいです。

https://reactjs.org/docs/hooks-reference.html#basic-hooks

## useMemo とは

`useCallback`同様、パフォーマンス最適化用のHookです。

`useCallback`との違いは以下です。

- `useCallback`は関数自体をメモ化
- `useMemo`は関数の結果をメモ化（メモ化された値を返すHook）

`useMemo`は、値を算出するための不要な再計算をスキップすることでパフォーマンスを向上させます。

### サンプルコード

こんなん作るとします。`useCallback`の時とほとんど同じです。

身長だけ、奇数か偶数か判定されています。

![スクリーンショット 2021-01-28 2.01.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/d2f6cdc9-e21d-652e-7bb4-cfd0d3ac7f5d.png)

差異があるファイルだけ以下に記載しておきます。

<details><summary>サンプルコード</summary><div>

```js:components/App.js
import React, { useState, useCallback } from "react";
import "./styles.css";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = useCallback(() => setHeight(height + 1), [height]);
  const incrementWeight = useCallback(() => setWeight(weight + 1), [weight]);

  const isEven = () => {
    console.log("身長");
    return height % 2 === 0;
  };

  return (
    <>
      <p>
        身長 {height} は （{isEven() ? "偶数" : "奇数"}）
      </p>
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;
```

</div></details>

コードを実行すると、どちらのボタンをクリックしても、発火します。

理由は、`useCallback`の時と同様に、コンポネントが再生成されたタイミングで`isEven`関数も再作成、実行されるからです。

本来は、この関数は身長ボタンがクリックされたときだけ実行するべきですよね？

でも、体重ボタンをクリックしても発火してしまう。。

これを問題として、`useMemo`を使って、身長ボタンがクリックされたときだけ`isEven`が実行されるようにしましょう。

<details><summary>サンプルコード</summary><div>

```js:components/App.js
import React, { useState, useCallback, useMemo } from "react";
import "./styles.css";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = useCallback(() => setHeight(height + 1), [height]);
  const incrementWeight = useCallback(() => setWeight(weight + 1), [weight]);

  const isEven = useMemo(() => {
    console.log("身長");
    return height % 2 === 0;
  }, [height]);

  return (
    <>
      <p>
        身長 {height} は （{isEven ? "偶数" : "奇数"}）
      </p>
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;
```

</div></details>

変更点は、赤枠。👇

![musing-almeida-g5ogr_-_CodeSandbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/88fe6f5d-a2fc-7d97-d4e2-435a3d45b9ea.png)

`useMemo`は、値（今回は bool値）を算出するための不要な再計算（今回なら「体重+1」ボタンクリック時の`isEven`内の計算処理）をスキップさせています。

「体重+1」ボタンクリック時は、`useMemo`が`isEven`処理を再実行させずに、結果（bool値）のみを返しています。

仮に、`isEven`処理が重い場合、「体重+1」ボタンクリック時は、結果（bool値）のみを返すので、パフォーマンス向上につながると思います。

今回は以上です。

## 参考

https://ja.reactjs.org/tutorial/tutorial.html