【React】ちゃんと知って使うReact.memo
この記事は Wano Group Advent Calendar 2022の10日目の記事となります。
はじめに
改めてですが、私は音楽ディストリビューションサービス「TuneCore Japan」のフロントエンドエンジニアを担当しております。そしてこの2022年も1年通してReact
をゴリゴリ書き、コードレビューも何度もしてきました。。TailWind CSS
だけをひたすら書いてた時期もちょっとありましたが
そんな中、自分の中のもやもやとして上がっていたのがReact.memoによるパフォーマンス改善をやるべきコンポーネントとやるべきでないコンポーネントの明確な定義ができていない
というところでした。この点にフォーカスして今回は React.memoをちゃんと知り、正しく使えるようにするために自分が調べたこと
を整理したいと思います。
React.memoに関する公式での説明
以下、Facebookが提供している公式Docから引用します。
React.memo は高階コンポーネントです。
もしあるコンポーネントが同じ props を与えられたときに同じ結果をレンダーするなら、結果を記憶してパフォーマンスを向上させるためにそれを React.memo でラップすることができます。つまり、React はコンポーネントのレンダーをスキップし、最後のレンダー結果を再利用します。
React.memo は props の変更のみをチェックします。React.memo でラップしているあなたのコンポーネントがその実装内で useState、useReducer や useContext フックを使っている場合、state やコンテクストの変化に応じた再レンダーは発生します。
デフォルトでは props オブジェクト内の複雑なオブジェクトは浅い比較のみが行われます。比較を制御したい場合は 2 番目の引数でカスタム比較関数を指定できます。
これはパフォーマンス最適化のためだけの方法です。バグを引き起こす可能性があるため、レンダーを「抑止する」ために使用しないでください。
自分なりの言葉で上記の説明を補足すると
React.memo
は、props
オブジェクト内の値に対して、 Object.isを通した値の比較を適用して、仮にtrutyだった場合はレンダーフェーズにおける仮想DOMの差異チェックをスキップするReact.memo
でラップしているコンポーネント内部のstate
の変更に対しては、従来通り再レンダーが走る
といった感じでしょうか。ここで注意したいことは、React.memo
でコンポーネントをラップした場合、追加で Object.is
によるprops
同士の浅い比較がレンダリングごとの計算処理として追加されていることになります。
なので、仮に各レンダーフェーズにおいて毎回Propsの変更が行われるコンポーネントがあった場合は、React.memo
でラップしても毎回再レンダリングされた上、追加でObject.is
によるprops
同士の浅い比較が行われるため結局総合的な計算量は増え、パフォーマンスに悪影響を及ぼすことになります。このことからも、慎重に利用するべきライブラリであることは間違いありません。
ではどういった時にReact.memo
を使えば良いのか
上記のような仮説を自分なりに持って調査をしている時に出会った記事が
になります。記事の第二章「2. When to use React.memo」が今回の疑問の解決に役立ちました。
詳細は記事を参照していただきたいですが、基本的な方針は 同じProps
で再レンダリングされる頻度が高い場合に React.memo
を適用する、というところです。
筆者が記事の中で紹介している コンポーネント Movie
と MovieViewsRealtime
のケースが理解しやすいと思います。
MovieViewsRealtime
コンポーネントはリアルタイムに情報が変わる頻度が高い箇所とstaticな情報をもつ箇所(title
やreleaseDate
)にDOM要素が分かれており
staticな情報をもつ箇所をMovie
コンポーネントとして切り出して React.memo
でラップすることで無駄な再レンダリングを防ぐ、と言うアプローチです。
このように、親コンポーネントの再レンダリングにつられて不必要な再レンダリングが子コンポーネントで発生するようなケースは多くの現場でのReactを通したUI開発でも肌感でよく起きている印象なので、このような場面においてはReact.memo
が有効だと思いました。
また、筆者が「3. When to avoid React.memo」において指摘しているように、React.memo
が悪影響を及ぼすパターンも多くあります。個人的にありがちだと思ったのが、コールバックのメモ化を怠った結果、常にReact.memo
によるObject.is
の値がfalseを返し意図しない再レンダリングが毎回行われてしまっていたと言うケースです。
さいごに
Reactの文脈においては、レンダリングの回数が減ったことが、そのままパフォーマンス向上につながったと考えられる傾向にあると予想しますが、コンポーネントによっては、React.memo
によるpropsの比較が悪影響を及ぼし結果としてパフォーマンスに問題を起こすことは、今回の調査によって注意しなければならない、と改めて思いました。
Reactの公式サイトにも書かれていますが、React.memo
に上記のようなリスクがあることは明らかなので、やはりレンダリング頻度や計算時間などを確認していくことは間違いなく必要になってくると思います。自分もProfilingを運用上で適切に活用して、より高品質なUI開発ができるように気をつけて行きたいところです。