【React】React.memoによる表示パフォーマンス改善

以前、コード分割によるReactの表示パフォーマンスの改善についてまとめました。
それに引き続き、今回は`React.memo`が表示にどのような影響を与えるのかを検証し、その内容をまとめました。

https://qiita.com/suzuki0430/items/9c3c4c4f3b25da5fcdd6

#`React.memo`とは
`React.memo`は高階コンポーネント(あるコンポーネントを受け取って新規のコンポーネントを返すような関数)です。

https://ja.reactjs.org/docs/higher-order-components.html#:~:text=%E9%AB%98%E9%9A%8E%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%20(higher%2Dorder%20component,React%20%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E5%BF%9C%E7%94%A8%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF%E3%81%A7%E3%81%99%E3%80%82&text=%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AB%E3%81%AF%E3%80%81%E9%AB%98%E9%9A%8E,%E8%BF%94%E3%81%99%E3%82%88%E3%81%86%E3%81%AA%E9%96%A2%E6%95%B0%E3%81%A7%E3%81%99%E3%80%82

あるコンポーネントに同じpropsを与えられて同じ結果をレンダーするとき、そのコンポーネントを`React.memo`でラッピングしてあげることで、結果を記憶させることができます。
Reactが記憶したレンダー結果を再利用することで、表示パフォーマンスを向上させることができます。

ただ、**`React.memo`はパフォーマンス最適化のためだけの方法なので、レンダーを「抑止する」ために使用することは推奨されていません。**
バグを引き起こす可能性があるので、下手に使わないほうがいいということですね。

ちなみに、Classコンポーネントで同様の実装を行うには、`React.PureComponent`や`shouldComponentUpdate()`というライフサイクルメソッドを使用します。

https://ja.reactjs.org/docs/react-component.html#shouldcomponentupdate

#検証
Toggle Personで`Person`コンポーネントを表示させて、Increase CountでButton Countを1ずつ増やしていくようなケースを考えました。
![スクリーンショット 2021-05-10 22.03.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/45cd1139-6263-1ddc-422e-a7706bf20cd4.png)

`Person`コンポーネントは`App`コンポーネント内部にあります。
![スクリーンショット 2021-05-10 22.11.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/c61788cd-c253-3367-39b0-eb2dda48a6dd.png)

##`App`の作成
`Person`の親コンポーネントである`App`を作成します。
`showPerson`の状態(true/false)で`Person`の表示を切り替えます。

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

import Person from './person.component';

const App = () => {

  const initialState = {
    count: 0,
    person: { name: 'Jack', age: 22 },
    showPerson: false,
  }

  const [state, setState] = useState(initialState)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {state.showPerson ? <Person person={state.person} /> : null}
        Button Count: {state.count}
        <button
          onClick={() =>
            setState((prevState) => ({
              ...prevState,
              count: prevState.count + 1,
            }))
          }
        >
          Increase Count
        </button>
        <button
          onClick={() =>
            setState((prevState) => ({
              ...prevState,
              showPerson: !prevState.showPerson,
            }))
          }
        >
          Toggle Person
        </button>
      </header>
    </div>
  );
}

export default App;
```

##`Person`の作成
`Person`コンポーネントをつくります。

###`React.memo`未使用
通常のデフォルトエクスポートを行います。

```jsx:person.component.jsx
import React from 'react';

const Person = ({ person }) => {
  console.log('rendering');

  return (
    <div>
      <p>{person.name}</p>
      <p>{person.age}</p>
    </div>
  );
};

export default Person;
```

###`React.memo`使用
`Person`をエクスポートするときに`React.memo`でラッピングします。

```jsx:person.component.jsx
import React from 'react';

const Person = ({ person }) => {
  console.log('rendering');

  return (
    <div>
      <p>{person.name}</p>
      <p>{person.age}</p>
    </div>
  );
};

export default React.memo(Person);
```

##結果比較
Toggle Personを一回押し、Increase Countを何回か押してみたときの結果を比較しました。

##`Person`のレンダリング回数
`React.memo`を使用しない場合、Toggle Personのあとに一回レンダリングされ、Increase Countを押すごとにレンダリングされていきます。
このように、**親コンポーネントの`App`のstateが変化したときに子コンポーネントも再レンダーされます。**
![スクリーンショット 2021-05-10 22.23.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/707106b1-0508-b649-b2fa-6d3bacc0559a.png)

一方、`React.memo`を使用した場合には、一度レンダリングされたら何回ボタンを押そうと再レンダリングされません。
このように、`React.memo`を使うと、**`Person`のpropsの変更のみをチェックするため、親コンポーネントの`App`のstateが変化しても`Person`のレンダリングには影響がありません。**
![スクリーンショット 2021-05-10 22.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/085a2703-7001-25d3-60f9-193c7ff5588a.png)

##`Personの`レンダリング時間
`React.memo`を使用しない場合の`Person`の初期レンダリング時間は0.8 msです。
![スクリーンショット 2021-05-10 22.46.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/8c8801f7-dc8b-c31c-376e-e503f5111496.png)

一方、`React.memo`を使用した場合の`Person`の初期レンダリング時間は0.9 msと、使用しない場合よりも長くなります。
![スクリーンショット 2021-05-10 22.47.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/7ff5fa03-2c49-502d-efa9-4f97ca15fb14.png)

**メモ化に伴い、最初のレンダリングに関しては`React.memo`を使用した方が時間がかかってしまいます。**

このように、`React.memo`にはデメリットもあるので、表示速度をdev-toolで測りながら、必要なときだけ利用を検討していくのがベストらしいです。

#参考資料

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

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

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