React.memoについて
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
の特徴
-
props
が変わらない場合、再レンダリングされない -
props
の浅い比較 - カスタム比較関数
props
が変わらない場合、再レンダリングされない
通常、React は親コンポーネントが再レンダリングされると、その子コンポーネントも再レンダリングされます。しかし、React.memo
を使用することで、props
が変更されない限り再レンダリングをスキップできます。
例えば、次のコードではParentComponent
が再レンダリングされても、MemoizedChild
はprops
に変更がなければ再レンダリングされません。
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
は再レンダリングされません。MemoizedChild
はcount
という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
はすべてのコンポーネントに使うべきではなく、特定の状況で有効です。以下のような場面で役立ちます。
-
高頻度で再レンダリングされる親コンポーネント
親コンポーネントが頻繁に再レンダリングされるが、子コンポーネントのprops
はほとんど変わらない場合、React.memo
を使うことで無駄な再レンダリングを抑制できます。 -
再レンダリングコストが高いコンポーネント
複雑な計算やレンダリングを行うコンポーネントで、props
に変更がない場合に再レンダリングを防ぎたいときに有効です。 -
リストアイテムやテーブル行など、同じ構造を繰り返しレンダリング
リストやテーブルなどの同じ構造を繰り返しレンダリングする場面では、React.memo
を使うことで一部のアイテムのみの再レンダリングを抑えることができます。
まとめ
-
React.memo
は、関数コンポーネントの再レンダリングをprops
の変化がない限り抑制するための高階コンポーネント - 浅い比較による再レンダリングの最適化が行われるが、場合によってカスタム比較関数も利用可能。
- 全てのコンポーネントに使用するべきではなく、サイレン舵輪んぐのコストやパフォーマンスの最適化が必要な場合で使うべき
個人的な感想
毎度毎度、再レンダリングで関係のないコンポーネントを動かすのってコストがかかるんじゃないの?と思って調べてみましたが、結局のところ、React.memo
を使うことによる計算コストが存在するよって感じなんですね。(そりゃそうか…って感じではありますが)
とりあえずで使っておけばいいじゃんみたいなノリじゃなくて、ちゃんとリスクリワードを考えなきゃいけないよって話でした。
となってくると次の議論ではレンダリングコストと計算コストの比較が重要になってくるのではないでしょうか。
結局天秤にかかるコストの話はこの2つだと思うので、ここをどう把握するのかが大事になってくる気がしました。
また、React.memo
についてもラップする関数という認識を持つことでただ呪文的に行っているわけじゃないというのも認識出来ました。
あくまで、再レンダリングを制御する関数ってことですね。再レンダリングするかどうかの判断(計算)が、ここで行われており、この判断コストがどれほどのものかということが大事になってくるんでしょうね。この感覚値はいっぱいコードを書いていかなければ中々身につかないような気がしました。
コストのトレードオフの感覚が必要になるんだろうなぁと
また、React.memo
自体にも浅い比較の計算コストがかかる点です。ここで気になるのが、浅い比較というポイントです。多分プリミティブなら問題もないのでしょうが、objectや配列になってくると違うのかもしれません。
ここもより深堀をする必要があると思います。→浅い比較について
一旦の最終結論としては、「とりあえず全てにつけておく」ではなく、状況に応じて判断して使うべきなんだろうなと言ったところ
今回はChatGPTを用いながら学習したので、ソースはGPTへ
多分Reactを普通に使う分には簡単なのかもしれませんが、こういったパフォーマンス関係になると一気に奥が深くなるんでしょうね。
Discussion