LoginSignup
6

More than 3 years have passed since last update.

React.memoの第2引数は、使わないほうが安全そう

Posted at

関数コンポーネントをメモ化するためのReact.memoですが、あまり使われていない(そして、後述の理由により使わないほうがよさそうな)第2引数があります。

TL; DR

  • React.memoの第2引数は、前のpropsと新しいpropsが「同じか」を比較する関数
  • 比較判定と使用箇所が離れているため、バグの原因になりがち
  • メモ化した下位コンポーネントに必要な値だけ渡す、という形にしたほうがすんなり行きそう

第2引数について

詳細は公式先行する記事に譲りますが、React.memoの第2引数としてfunction areEqual(prevProps, nextProps)のような関数を渡せます。メモ化後のコンポーネントをrenderしようとした際にこの関数が呼び出されて、trueを返せばレンダリングしなくていい、ということになります。

機能的な注意点

  • クラスコンポーネントはshouldComponentUpdateという名前のとおり、更新したい場合にtrueを返しますが、React.memoの場合は逆になっています。
  • stateのチェックはできません(React.useStateで作ったstateを書き換えた場合、コンポーネントは再描画されます)。
  • あくまでこれは最適化のヒントで、「trueを返せばレンダリングされない」ことに依存するようなコードを書いてはいけません。

問題になる点

もちろん、第2引数の関数に意図したとおりの関数を渡せば、きちんとキャッシュ制御がなされます。問題になるのは、あとからこのコンポーネントを改造する場合です。

たとえば、関数内で「foobarが一致するか」を判定していた場合に、新しいbazというpropを追加してしまえば、これはチェックから漏れることになります。また、fooが巨大なオブジェクトの場合にも、どこまでを同じだと判定したかと、コンポーネント内でどう使うのかが全く別個に書けてしまうので、両者の変更がうまく噛み合わず、意図した更新がなされない、という事態にもなりかねません。

極限までチューニングしたいのであれば有用かもしれませんが、いわゆる「自分の足を撃ち抜ける」ような仕組みでもあるので、使わずに済むのならそのほうがありがたいと感じました。

代替案

幸い、ほぼ同等のパフォーマンスを、通常のReact.memoで実現することも可能です。

細かくコンポーネントを切り出す

下位のコンポーネントを細かく切り出して、それにReact.memoをかけることで、メモ化を適度な粒度で適用していく、という手段があります。たとえば、<select>1つとっても、中には多くの<option>があって、そのまま再描画させると中身もチェック対象になること、一般的な使い方で、ユーザーが1文字打つごとに<option>が切り替わる、なんて運用はそう多くないので、<select>と中身をまとめてメモ化したコンポーネントにする、というような感じで切り出せば、再描画を減らせます。

さらに、元のコンポーネント全体であれば再描画が必要になっていたところでも、一部だけ切り出せば再描画不要となる、という場面も考えられますので、さらに処理を削減できます。

値の変換だけして、中身は丸投げ

上の考え方を推し進めれば、「外部から使うコンポーネントでは値のフィルタリングだけ行って、残りの実装はすべてメモ化されたコンポーネントに投げる」という方法が考えられます。たとえば、propのうちbigObjectが変化し続けるけど、いま使いたいのはそのうちpropertyOnepropertyTwo、だけだったとします。

const Main = React.memo(function Main({propertyOne, propertyTwo, /* 後略 */}){
  // propertyOneやpropertyTwoを使って描画を行う
  // bigObjectは渡ってこない

  return < />;
});

function Frontend({bigObject, ...rest}){
  const {propertyOne, propertyTwo} = bigObject;
  // bigObjectは渡されない
  return <Main {...rest} propertyOne={propertyOne} propertyTwo={propertyTwo} />
}

このようにすることで、「必要な値だけをメモ化する」ことが実現できます。React.memoの2引数版との違いとして、Frontendでフィルタした変数は本当にMain渡されないので、「メモ化の基準として使わなかった値をうっかり参照してしまって更新異常になる」ことが起き得ない、というより安全な形となっています。速度的にも、関数判定より多少は遅くなりますが、メモ化しないバージョンよりはぐっと速くなります。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6