関数コンポーネントをメモ化するための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引数の関数に意図したとおりの関数を渡せば、きちんとキャッシュ制御がなされます。問題になるのは、あとからこのコンポーネントを改造する場合です。
たとえば、関数内で「foo
とbar
が一致するか」を判定していた場合に、新しいbaz
というpropを追加してしまえば、これはチェックから漏れることになります。また、foo
が巨大なオブジェクトの場合にも、どこまでを同じだと判定したかと、コンポーネント内でどう使うのかが全く別個に書けてしまうので、両者の変更がうまく噛み合わず、意図した更新がなされない、という事態にもなりかねません。
極限までチューニングしたいのであれば有用かもしれませんが、いわゆる「自分の足を撃ち抜ける」ような仕組みでもあるので、使わずに済むのならそのほうがありがたいと感じました。
代替案
幸い、ほぼ同等のパフォーマンスを、通常のReact.memo
で実現することも可能です。
細かくコンポーネントを切り出す
下位のコンポーネントを細かく切り出して、それにReact.memo
をかけることで、メモ化を適度な粒度で適用していく、という手段があります。たとえば、<select>
1つとっても、中には多くの<option>
があって、そのまま再描画させると中身もチェック対象になること、一般的な使い方で、ユーザーが1文字打つごとに<option>
が切り替わる、なんて運用はそう多くないので、<select>
と中身をまとめてメモ化したコンポーネントにする、というような感じで切り出せば、再描画を減らせます。
さらに、元のコンポーネント全体であれば再描画が必要になっていたところでも、一部だけ切り出せば再描画不要となる、という場面も考えられますので、さらに処理を削減できます。
値の変換だけして、中身は丸投げ
上の考え方を推し進めれば、「外部から使うコンポーネントでは値のフィルタリングだけ行って、残りの実装はすべてメモ化されたコンポーネントに投げる」という方法が考えられます。たとえば、propのうちbigObject
が変化し続けるけど、いま使いたいのはそのうちpropertyOne
とpropertyTwo
、だけだったとします。
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
へ渡されないので、「メモ化の基準として使わなかった値をうっかり参照してしまって更新異常になる」ことが起き得ない、というより安全な形となっています。速度的にも、関数判定より多少は遅くなりますが、メモ化しないバージョンよりはぐっと速くなります。