🛠️

React.memoの浅い比較について

2024/11/11に公開

React.memoの浅い比較について

浅い比較の仕組み

  • プリミティブ型の場合は、値そのものを比較します。例えば、count={1}からcount={1}のように同じ値の場合、React.memoは再レンダリングをスキップします。
  • オブジェクト型や配列型の場合、値ではなく**参照(メモリ上の位置)**を比較します。そのため、propsの中にオブジェクトや配列が含まれている場合、たとえ内容が同じでも、毎回新しいオブジェクトや配列が作られていれば参照が異なるため、再レンダリングが発生してしまいます。
const Component = React.memo(({ obj }) => {
  console.log("Component rendered");
  return <div>{obj.value}</div>;
});

const App = () => {
  const obj = { value: 1 };
  return <Component obj={obj} />;
};

この場合、毎回Appがレンダリングされるとobjは新しいオブジェクトとして生成されるので、オブジェクトの参照が毎回異なり、React.memoでも再レンダリングが発生してしまいます。

配列やオブジェクトが原因で再レンダリングが発生する例

const Component = React.memo(({ arr }) => {
  console.log("Component rendered");
  return <div>{arr.join(", ")}</div>;
});

const App = () => {
  const arr = [1, 2, 3]; // 新しい配列が毎回生成される
  return <Component arr={arr} />;
};

この例では、arrは毎回新しい配列が生成されるため、浅い比較では参照が異なっていると判断され、毎回再レンダリングが発生します。

浅い比較を最適化する方法

  • useCallbackuseMemoの使用
    配列やオブジェクトの再生成を防ぐために、useCallbackuseMemoを使って同じ参照を保持することができます。
const App = () => {
  const arr = useMemo(() => [1, 2, 3], []); // 配列をメモ化
  return <Component arr={arr} />;
};

これにより、arrは最初に生成された後、同じ参照を持ち続けるため、不要な再レンダリングを防ぐことができます。

まとめ

  • 浅い比較
    プリミティブ型は値を比較し、オブジェクトや配列は参照が同じかどうかを比較します。
  • プリミティブ型propsならReact.memoで効果的に再レンダリングを抑制できますが、配列やオブジェクト型の場合、毎回新しいものが生成されると再レンダリングが発生します。
  • オブジェクトや配列を再生成しない工夫(useMemouseCallbackを使うなど)が、パフォーマンス向上に役立ちます。

個人的感想

shallowとdeepはどっかで知る機会があったので、なんとなくプリミティブ型と配列やオブジェクトなどのメモリ参照値なんだろうなと思っていましたが、案の定でした。
配列やオブジェクト型の抑制として、再生成しない工夫が必要とのことで、ここでようやくuseMemouseCallbackが効いてくるんですね。
よくよく考えてみれば、useMemoみたいに計算を端折るパターンはこういったところで特に刺さりそうですね。
useCallbackは正直イマイチまだ認識が甘いので、何とも言えないです。
ここはまた追って学習しなければならないポイントになるかと思います。
にしても部分的なレンダリングやレンダリングの最適化が柔軟に行える反面、適切なパフォーマンス調整が果たして人間の範疇でどこまでできるのやらという気持ちはあります。
ここら辺、コツやツボがあるよ!って分かる方いらっしゃいましたら是非教えて頂けると幸いです。

Discussion