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


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

React.memoとは

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

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

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

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

検証

Toggle PersonでPersonコンポーネントを表示させて、Increase CountでButton Countを1ずつ増やしていくようなケースを考えました。

PersonコンポーネントはAppコンポーネント内部にあります。

Appの作成

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

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未使用

通常のデフォルトエクスポートを行います。

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でラッピングします。

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が変化したときに子コンポーネントも再レンダーされます。

一方、React.memoを使用した場合には、一度レンダリングされたら何回ボタンを押そうと再レンダリングされません。
このように、React.memoを使うと、Personのpropsの変更のみをチェックするため、親コンポーネントのAppのstateが変化してもPersonのレンダリングには影響がありません。

Personのレンダリング時間

React.memoを使用しない場合のPersonの初期レンダリング時間は0.8 msです。

一方、React.memoを使用した場合のPersonの初期レンダリング時間は0.9 msと、使用しない場合よりも長くなります。

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

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

参考資料