📝

React.memoについて

2024/11/11に公開

React.memo

React.memoは、関数コンポーネントの再レンダリングを制御するための高階コンポーネント(Higher Order Component)です。propsが変わらない限り、そのコンポーネントの再レンダリングを防ぐことができます。これは、パフォーマンスの最適化に役立ち、特に大規模なアプリケーションや高頻度の再レンダリングが発生するケースで効果的です。

使い方

React.memoの使い方は以下のような感じです。

const MyComponent = React.memo((props) => {
  return <div>{props.name}</div>;
});

この例では、MyComponent はprops.nameが変わらない限り再レンダリングされません。


React.memoの特徴

  1. propsが変わらない場合、再レンダリングされない
  2. propsの浅い比較
  3. カスタム比較関数

propsが変わらない場合、再レンダリングされない

通常、React は親コンポーネントが再レンダリングされると、その子コンポーネントも再レンダリングされます。しかし、React.memoを使用することで、propsが変更されない限り再レンダリングをスキップできます。
例えば、次のコードではParentComponentが再レンダリングされても、MemoizedChildpropsに変更がなければ再レンダリングされません。

const ChildComponent = ({ count }) => {
  console.log("ChildComponent rendered");
  return <div>Count: {count}</div>;
};

const MemoizedChild = React.memo(ChildComponent);

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <button onClick={() => setOtherState(otherState + 1)}>
        Change Other State
      </button>
      <MemoizedChild count={count} />
    </div>
  );
}

このコードでは、otherStateが更新されたとしてもMemoizedChildは再レンダリングされません。MemoizedChildcountというpropsに依存しており、それが変わらない限りレンダリングはスキップされます。

propsの浅い比較

React.memoは、デフォルトでprops浅い比較(shallow comparison)を行います。つまり、オブジェクトや配列がpropsに渡される場合、その中身までは比較されず、参照の変更があったかどうかのみがチェックされる。

const MyComponent = React.memo(({ obj }) => {
  return <div>{obj.name}</div>;
});

// 親コンポーネントで毎回新しいオブジェクトを渡すと、再レンダリングが発生する
<MyComponent obj={{ name: "John" }} />

この例では、毎回新しいオブジェクトが渡されるため、React.memoを使っていても再レンダリングされてしまいます。もしオブジェクトの中身を比較したい場合は、React.memoの第二引数にカスタムの比較関数を渡すことができます。

カスタム比較関数

React.memoの第二引数にカスタムの比較関数を渡すことで、再レンダリングの条件を細かく制御することができます。

const MyComponent = React.memo(
  ({ obj }) => {
    return <div>{obj.name}</div>;
  },
  (prevProps, nextProps) => {
    // オブジェクトの中身を比較する
    return prevProps.obj.name === nextProps.obj.name;
  }
);

この場合、obj.nameが変更されない限り、再レンダリングが発生しなくなります。

React.memoの適用場面

React.memoはすべてのコンポーネントに使うべきではなく、特定の状況で有効です。以下のような場面で役立ちます。

  1. 高頻度で再レンダリングされる親コンポーネント
    親コンポーネントが頻繁に再レンダリングされるが、子コンポーネントのpropsはほとんど変わらない場合、React.memoを使うことで無駄な再レンダリングを抑制できます。
  2. 再レンダリングコストが高いコンポーネント
    複雑な計算やレンダリングを行うコンポーネントで、propsに変更がない場合に再レンダリングを防ぎたいときに有効です。
  3. リストアイテムやテーブル行など、同じ構造を繰り返しレンダリング
    リストやテーブルなどの同じ構造を繰り返しレンダリングする場面では、React.memoを使うことで一部のアイテムのみの再レンダリングを抑えることができます。

まとめ

  • React.memoは、関数コンポーネントの再レンダリングをpropsの変化がない限り抑制するための高階コンポーネント
  • 浅い比較による再レンダリングの最適化が行われるが、場合によってカスタム比較関数も利用可能。
  • 全てのコンポーネントに使用するべきではなく、サイレン舵輪んぐのコストやパフォーマンスの最適化が必要な場合で使うべき

個人的な感想

毎度毎度、再レンダリングで関係のないコンポーネントを動かすのってコストがかかるんじゃないの?と思って調べてみましたが、結局のところ、React.memoを使うことによる計算コストが存在するよって感じなんですね。(そりゃそうか…って感じではありますが)
とりあえずで使っておけばいいじゃんみたいなノリじゃなくて、ちゃんとリスクリワードを考えなきゃいけないよって話でした。
となってくると次の議論ではレンダリングコストと計算コストの比較が重要になってくるのではないでしょうか。
結局天秤にかかるコストの話はこの2つだと思うので、ここをどう把握するのかが大事になってくる気がしました。
また、React.memoについてもラップする関数という認識を持つことでただ呪文的に行っているわけじゃないというのも認識出来ました。
あくまで、再レンダリングを制御する関数ってことですね。再レンダリングするかどうかの判断(計算)が、ここで行われており、この判断コストがどれほどのものかということが大事になってくるんでしょうね。この感覚値はいっぱいコードを書いていかなければ中々身につかないような気がしました。
コストのトレードオフの感覚が必要になるんだろうなぁと
また、React.memo自体にも浅い比較の計算コストがかかる点です。ここで気になるのが、浅い比較というポイントです。多分プリミティブなら問題もないのでしょうが、objectや配列になってくると違うのかもしれません。
ここもより深堀をする必要があると思います。→浅い比較について
一旦の最終結論としては、「とりあえず全てにつけておく」ではなく、状況に応じて判断して使うべきなんだろうなと言ったところ

今回はChatGPTを用いながら学習したので、ソースはGPTへ
多分Reactを普通に使う分には簡単なのかもしれませんが、こういったパフォーマンス関係になると一気に奥が深くなるんでしょうね。

Discussion