【React】レンダリングの最適化にReact.memoとuseCallbackとReact.useMemoを使う

# レンダリングの最適化にmemoとuseCallbackとReact.useMemoを使う
Reactの開発は、再レンダリングを意識しながら開発をすることで、パフォーマンスの改善につなげることができます。
再レンダリングを制御するためにメモ化を行います。メモ化する技術のReact.memoとuseCallbackとReact.useMemoをまとめていきます。

## メモ化の種類

Reactはメモ化することで、再レンダリングを制御・最適化することができます。
メモ化は前回の処理結果を保持しておくことで、処理を高速化することができます。

|種類  |対象  |
|---|---|
|React.memo  |コンポーネント  |
|useCallback  |コールバック関数  |
|React.useMemo|変数|


## 再レンダリングが起きる条件

1. Stateが更新されたコンポーネント
1. Propｓが変更されたコンポーネント
1. 再レンダリングされたコンポーネント配下のすべてのコンポーネント



###　再レンダリングを起こしてみる

```javascript:/src/index.js
import ReactDom from "react-dom";
import { App } from "./App";

ReactDom.render(<App />, document.getElementById("root"));
);

```

```jsx:/src/App.jsx
import { useState, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";

export const App = () => {
  console.log("App レンダリング ");

  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum(num + 1);
  };

  const onClickReset = useCallback(() => {
    setNum(0);
  }, []);

  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
      <Child1 onClickReset={onClickReset} />
      <Child4 />
    </>
  );
};
```

```jsx:/src/components/Child1.jsx
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";

const style = {
  backgroundColor: "#AAFFFF	",
};

export const Child1 = (props) => {
  console.log("Child1 レンダリング ");

  const { onClickReset } = props;

  return (
    <div style={style}>
      <p>Child1</p>
      <button onClick={onClickReset}>リセット</button>
      <Child2 />
      <Child3 />
    </div>
  );
};

```

```jsx/src/components/Child2.jsx
const style = {
  backgroundColor: "#FFCCFF",
};

export const Child2 = () => {
  console.log("Child2 レンダリング ");

  return (
    <div style={style}>
      <p>Child2</p>
    </div>
  );
};
```

```jsx/src/components/Child3.jsx
const style = {
  backgroundColor: "#FFCCFF",
};

export const Child3 = () => {
  console.log("Child3 レンダリング ");

  return (
    <div style={style}>
      <p>Child3</p>
    </div>
  );
};
```

```jsx;/src/components/Child4.jsx
const style = {
  backgroundColor: "#66FF00",
};

export const Child4 = () => {
  console.log("Child4 レンダリング ");

  return (
    <div style={style}>
      <p>Child4</p>
    </div>
  );
};
```

|![FireShot Capture 085 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/668e2446-02d8-54c1-57ce-ce9dfba11a3c.png)|
|:--|

上記のコードで実行した後、カウントアップのボタンを押すと下記のようにすべてのコンポーネントが再レンダリングされていることが確認できます。

![スクリーンショット 2021-09-30 23.38.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/3cb5b1d7-4c09-33b0-07f6-0cbce24cde8e.png)

## コンポーネントのメモ化にReact.memoを使う

React.memoは、コンポーネントをメモ化します。上記のコードのコンポーネントをReact.memoを使うように変更します。

https://ja.reactjs.org/docs/react-api.html#reactmemo

```react:React.memoの書式
const Component = memo(() =>{});
```

```jsx:/src/App.jsx
import { useState, memo, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";

export const App = memo(() => { // memoに変更した
  console.log("App レンダリング ");

  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum(num + 1);
  };

  const onClickReset = useCallback(() => {
    setNum(0);
  }, []);

  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
      <Child1 onClickReset={onClickReset} />
      <Child4 />
    </>
  );
});
```

```jsx/src/components/Child1.jsx
import { memo } from "react";
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";

const style = {
  backgroundColor: "#AAFFFF	",
};

export const Child1 = memo((props) => { // memoに変更した
  console.log("Child1 レンダリング ");

  const { onClickReset } = props;

  return (
    <div style={style}>
      <p>Child1</p>
      <button onClick={onClickReset}>リセット</button>
      <Child2 />
      <Child3 />
    </div>
  );
});

```

```jsx/src/components/Child2.jsx
import { memo } from "react";

const style = {
  backgroundColor: "#FFCCFF",
};

export const Child2 = memo(() => { // memoに変更した
  console.log("Child2 レンダリング ");

  return (
    <div style={style}>
      <p>Child2</p>
    </div>
  );
});
```

```jsx/src/components/Child3.jsx
import { memo } from "react";

const style = {
  backgroundColor: "#FFCCFF",
};

export const Child3 = memo(() => { // memoに変更した
  console.log("Child3 レンダリング ");

  return (
    <div style={style}>
      <p>Child3</p>
    </div>
  );
});
```

```jsx;/src/components/Child4.jsx
import { memo } from "react";

const style = {
  backgroundColor: "#66FF00",
};

export const Child4 = memo(() => {
  console.log("Child4 レンダリング ");

  return (
    <div style={style}>
      <p>Child4</p>
    </div>
  );
});
```

|![FireShot Capture 086 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/8967cb7a-58f7-6913-ef3d-b1578f05b768.png)|
|:--|


メモ化したことで、Appコンポーネントのみ再レンダリングされていることが確認することができました。

![スクリーンショット 2021-10-01 0.03.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/380f29f2-f770-9776-dacc-a11a4193e969.png)


## 関数による再レンダリングを起こしてみる

カウンアップをリセットするボタンの関数を作成し、関数をChild1に渡すコードを作成しました。実行し、カウントアップを行うと先程メモ化した、Chile1の再レンダリングが発生してしまいます。

|![FireShot Capture 087 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/a81b38e2-f58c-ef6e-0802-45dafa91e5aa.png)|
|:--|

|![スクリーンショット 2021-10-01 23.46.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/643dab20-eea7-b7d5-eb45-fa4b602ec8f0.png)|
|:--|

```jsx:/src/App.jsx
import { useState, memo } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";

export const App = memo(() => {
  console.log("App レンダリング ");

  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum(num + 1);
  };

// 追加しました
  const onClickReset = () => {
    setNum(0);
  };

  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
      <Child1 onClickReset={onClickReset} />　// 関数の受け渡しを追加しました
      <Child4 />
    </>
  );
});

```

```jsx/src/components/Child1.jsx
import { memo } from "react";
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";

const style = {
  backgroundColor: "#AAFFFF	",
};

export const Child1 = memo((props) => {
  console.log("Child1 レンダリング ");

  const { onClickReset } = props;

  return (
    <div style={style}>
      <p>Child1</p>
      <button onClick={onClickReset}>リセット</button>
      <Child2 />
      <Child3 />
    </div>
  );
});

```

## 関数のメモ化にuseCallbackを使ってみる
Reactの関数のメモ化は、useCallbackを使います。onClickReset関数を下記のようにuseCallbackで書きます。第１引数に関数を書き、第２引数には依存配列の要素を書きます。[ ]の場合は、レンダリングされた最初の１回のみ読み込まれます。２回目からは最初に作成されたものが使われます。

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


```react:/src/App.jsx
import { useState, memo, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";

export const App = memo(() => {
  console.log("App レンダリング ");

  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum(num + 1);
  };

  const onClickReset = useCallback(() => { //useCallbackを使うように変更した
    setNum(0);
  }, []);

  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
      <Child1 onClickReset={onClickReset} />
      <Child4 />
    </>
  );
});

```

Child1が、再レンダリングされなくなりました。

|![スクリーンショット 2021-10-01 23.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/6bb37a9c-1b88-6df6-6e4d-85d1f0aff875.png)|
|:--|

## 変数のメモ化にReact.useMemoを使う
変数や値のメモ化には、React.useMemoを使います。第１引数に計算式、第２引数に依存配列の要素を書きます。依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します。下記のコードの場合、aとbの値が変わらない場合、最初にレンダリングした時の値が使い回されます。

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

```react:React.useMemo
const memoizedValue = useMemo(() => {
    return a + b;
    }, [a, b]);
```