React入門 - Tips12 - React.memo, useCallback, useMemoで無駄なレンダリングを抑制

# React.memo, useCallBack, useMemoを使う理由

<font color="red">パフォーマンス改善のために使う。です。</font>

React.memo, useCallback, useMemoをちゃんと使うと`メモ化`され無駄なレンダリングが抑制されます。

メモ化とは、以下です。

- 再レンダリングされても同じ結果になるものは、初回のものを記録する。
- 値を記録し再レンダリングされても値を計算しない。 ※値が変化するときのみ処理する。

よく分からないですね。
後半のサンプル動かしながら覚えていきましょう。

<br>
Reactをやり始めの頃は、ただただ関数にuseCallbackを付けたら、良いんだろ？
と思い実装していました。

上記考え、やり方は無駄な実装です。
（厳密にいうと逆に、useCallbackを処理する分、パフォーマンス悪くなるとの意見もあります。）
ちゃんと理解して使用しましょうということです。

# レンダリングが起こる条件

[ここ](https://qiita.com/jonakp/items/0db6fb9e75edcec875b2)と[ここ](https://qiita.com/seira/items/9e38204758030cd5442a)がとても分かりやすかったです。特に[ここ](https://qiita.com/seira/items/9e38204758030cd5442a)のサンプルを動かすと理解が深まると思います。


Reactコンポーネントの再レンダリングは、おおよそ以下3つの条件で発生します。

1. propsの更新
2. stateの更新（自コンポーネントのstate）
3. 親コンポーネントが再レンダリングされた時

<font color="red">※一つでも条件に当てはまれば、再レンダリングされるということです。</font>

# 素のサンプルコードを動かしてみよう

<font color="red">メモ化を施していない素のソースです。</font>

```typescript
import React, { useState } from "react";

/**
 * 親コンポ
 * @returns
 */
export const App: React.FC = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  //Aボタンのstateセット用関数
  const incrementACounter = () => setCountStateA(countStateA + 1);

  //Bボタンのstateセット用関数
  const incrementBCounter = () => setCountStateB(countStateB + 1);

  return (
    <>
      <Count text="A ボタン" countState={countStateA} />
      <Count text="B ボタン" countState={countStateB} />
      <button onClick={incrementACounter}>A ボタン</button>
      <button onClick={incrementBCounter}>B ボタン</button>
    </>
  );
};
export default App;

/**
 * 子コンポ
 */
const Count: React.FC<{ text: string; countState: any }> = ({
  text,
  countState,
}) => {
  console.log("Count child component:", text);
  return (
    <p>
      {text}:{countState}
    </p>
  );
};
```

Aボタン押下時に、Bボタンも描画されています。
Bボタン押下時に、Aボタンも描画されています。

![react-memo1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/8a3a7629-8b5e-d948-9eff-93de7749f398.gif)

# React.memoを使用してみます。

先程のソースの子コンポを`React.memo`でラップします。
へい！よー！

```typescript
import React, { useState } from "react";

/**
 * 親コンポ
 * @returns
 */
export const App: React.FC = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  //Aボタンのstateセット用関数
  const incrementACounter = () => setCountStateA(countStateA + 1);

  //Bボタンのstateセット用関数
  const incrementBCounter = () => setCountStateB(countStateB + 1);

  return (
    <>
      <Count text="A ボタン" countState={countStateA} />
      <Count text="B ボタン" countState={countStateB} />
      <button onClick={incrementACounter}>A ボタン</button>
      <button onClick={incrementBCounter}>B ボタン</button>
    </>
  );
};
export default App;

/**
 * 子コンポ
 */
// const Count: React.FC<{ text: string; countState: any }> = ({ ★ ここを変えました。
const Count: React.FC<{ text: string; countState: any }> = React.memo(
  ({ text, countState }) => {
    console.log("Count child component:", text);
    return (
      <p>
        {text}:{countState}
      </p>
    );
    // }; ★ ここを変えました。
  }
);
```

Aボタン押下時に、<font color="red">Bボタンは描画されなくなりました。</font>
Bボタン押下時に、<font color="red">Aボタンは描画されなくなりました。</font>

![react-memo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/89f3928d-cdd4-2f91-7a2e-c379be3e3083.gif)

<font color="red">無駄な描画を抑制できました。</font>

---

# propsに関数がある場合

先程のReact.memoで無駄な描画を抑制できましたが、
原理を簡単に言うとpropsの中身が変わらなければ描画しない。
ということです。

しかし、propsが関数になると、オブジェクト？なので、毎度前回とは等価ではないとReactは判断するようです。

以下のソースを試してみましょう。
（前のソースを一部書き換えます。）

```typescript
import React, { MouseEventHandler, useState } from "react";

/**
 * 親コンポ
 * @returns
 */
export const App: React.FC = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  //Aボタンのstateセット用関数
  const incrementACounter = () => setCountStateA(countStateA + 1);

  //Bボタンのstateセット用関数
  const incrementBCounter = () => setCountStateB(countStateB + 1);

  return (
    <>
      {/* ★ ここから書き換えます。 start */}
      {/* <Count text="A ボタン" countState={countStateA} />
      <Count text="B ボタン" countState={countStateB} />
      <button onClick={incrementACounter}>A ボタン</button>
      <button onClick={incrementBCounter}>B ボタン</button> */}
      <p>A ボタン: {countStateA} </p>
      <p>B ボタン: {countStateB} </p>
      <Button counterState={incrementACounter} buttonValue="Aボタン" />
      <Button counterState={incrementBCounter} buttonValue="Bボタン" />
      {/* ★ end */}
    </>
  );
};
export default App;

// ★ 追加 start
/**
 * 子コンポ（Button）※React.memoされています。
 */
const Button: React.FC<{
  counterState: MouseEventHandler<HTMLButtonElement> | undefined;
  buttonValue: string;
}> = React.memo(({ counterState, buttonValue }) => {
  console.log("Button child component:", buttonValue);
  return <button onClick={counterState}>{buttonValue}</button>;
});
// ★ 追加 end

// ★ 以下削除
// /**
//  * 子コンポ
//  */
// // const Count: React.FC<{ text: string; countState: any }> = ({ ★ ここを変えました。
// const Count: React.FC<{ text: string; countState: any }> = React.memo(
//   ({ text, countState }) => {
//     console.log("Count child component:", text);
//     return (
//       <p>
//         {text}:{countState}
//       </p>
//     );
//     // }; ★ ここを変えました。
//   }
// );
```

また、
Aボタン押下時に、Bボタンも描画されています。
Bボタン押下時に、Aボタンも描画されています。
の状態になりました。

![react-memo3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/59103fa3-7c84-20e9-8025-4b2d87a4f97f.gif)


あかん。。

# 関数をuseCallbackでメモ化

してみます。

```typescript
import React, { MouseEventHandler, useState, useCallback } from "react";

/**
 * 親コンポ
 * @returns
 */
export const App: React.FC = () => {
  const [countStateA, setCountStateA] = useState(0);
  const [countStateB, setCountStateB] = useState(0);

  //Aボタンのstateセット用関数
  // const incrementACounter = () => setCountStateA(countStateA + 1); //★ 書き換え
  const incrementACounter = useCallback(
    () => setCountStateA(countStateA + 1),
    [countStateA]
  );

  //Bボタンのstateセット用関数
  // const incrementBCounter = () => setCountStateB(countStateB + 1); // ★ 書き換え
  const incrementBCounter = useCallback(
    () => setCountStateB(countStateB + 1),
    [countStateB]
  );

  return (
    <>
      <p>A ボタン: {countStateA} </p>
      <p>B ボタン: {countStateB} </p>
      <Button counterState={incrementACounter} buttonValue="Aボタン" />
      <Button counterState={incrementBCounter} buttonValue="Bボタン" />
    </>
  );
};
export default App;

/**
 * 子コンポ（Button）※React.memoされています。
 */
const Button: React.FC<{
  counterState: MouseEventHandler<HTMLButtonElement> | undefined;
  buttonValue: string;
}> = React.memo(({ counterState, buttonValue }) => {
  console.log("Button child component:", buttonValue);
  return <button onClick={counterState}>{buttonValue}</button>;
});
```

今度はちゃんと
Aボタン押下時に、<font color="red">Bボタンは描画されなくなりました。</font>
Bボタン押下時に、<font color="red">Aボタンは描画されなくなりました。</font>
に出来ました。

![react-memo4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/e5e60872-9239-e90b-7069-8e55f395c7f1.gif)

# useMemoを使ってみる。

先程は、`useCallback`を使って関数を`メモ化`しました。
今度は、値自体を`メモ化`します。



<font color="red">以下書きかけ</font>