【React】実務におけるuseMemo/useCallback/React.memoの適用方針

JavaScript

useMemouseCallbackReact.memoによるメモ化を活用することでReactアプリケーションのパフォーマンスの向上が期待できます。
しかしメモ化の効果や挙動について理解しても、実際の開発ではどのようなケースでメモ化を適用するべきかイマイチわかりにくいです。

そこで今回は、実際の開発においてどういったケースや判断材料をもとにuseMemouseCallbackReact.memoを適用するべきか個人的な考えをまとめたので紹介します。

結論: useMemo・useCallback・React.memoの個人的な適用方針

useMemoの適用方針
  • Propsとして利用されている・される可能性があるデータ(オブジェクト)ならば「とりあえず」適用する。
  • カスタムフックの戻り値のデータ(オブジェクト)には「とりあえず」適用する。
  • 結果の算出コストの高いデータ(オブジェクト)ならば「必要に応じて」適用する。
useCallbackの適用方針
  • Propsに渡されている関数ならば「とりあえず」適用する。
  • カスタムフックの戻り値の関数には「とりあえず」適用する。
  • useEffectの依存配列に定義されている関数ならば適用する。
React.memoの適用方針
  • レンダリングコストの高いコンポーネントに対して「必要に応じて」適用する。

上記の結論になった理由について以下で説明します。

前提知識: メモ化を利用したReactのパフォーマンスチューニングの基本的な流れ

ステップ1: メモ化対象のコンポーネントにあたりをつける

メモ化を利用したReactアプリケーションのパフォーマンスチューニングの基本方針は『レンダリングの頻度やコストが高いコンポーネントをメモ化し、不要なレンダリングを減らす』です。

コンポーネントのメモ化はReact.memoでコンポーネントを囲むことで実現できます。1
React.memoでコンポーネントを囲むことでPropsが変更されない限りレンダリングを省略できます。

ですので、パフォーマンスチューニングにはReact.memoを適用するコンポーネントを見つける作業が含まれます。
つまり、React.memoは『レンダリングコストの高いコンポーネントに対して「必要に応じて」適用する』ことになります。

ステップ2: メモ化対象のコンポーネントのPropsも必要に応じてメモ化する

関数コンポーネント内で定義された関数やオブジェクトはレンダリングのたびに再作成されます。

ですので、React.memoでメモ化する場合は当該コンポーネントに渡されるPropsをチェックする必要があります。
オブジェクトや関数がPropsで定義されているのであれば、コンポーネントと同様にPropsもメモ化する必要があります。
Propsのメモ化が適切にされていないと、データ上はPropsの値が変わっていないように見えるにもかかわらずコンポーネントのメモ化がうまく適用されないという現象が発生してしまいます。

この時に利用されるのがuseMemouseCallbackです。オブジェクトはuseMemo、関数はuseCallbackでメモ化できます。

関数やオブジェクトをあらかじめメモ化しておけばコンポーネントをメモ化する際にPropsを意識しなくて済む

上記で説明した通り、メモ化を利用したパフォーマンスチューニングの基本的な手順は『React.memoの適用 → 必要に応じてuseMemo・useCallbackをPropsに適用』です。

しかし結局useMemoはいつ使えばいいの? 僕の決定版でも言及されている通り、React.memoを適用する度にPropsをチェックするのは非効率です。

もし、Propsとして利用されている(あるいは利用される可能性のある)オブジェクトや関数にあらかじめuseMemouseCallbackが適用されていれば、React.memoを適用する際にPropsのメモ化を気にする必要がなくなります。
加えて、そこのお前! 余計なuseMemo1個に含まれるオーバーヘッドは余計なdiv 0.57個分だぜ!で紹介されている通り、useMemoを適用した際のオーバーヘッドは微々たるものです。

ですので、useMemoやuseCallbackは『Propsとして利用される可能性があるのであれば「とりあえず」適用する。』という結論になります。

また、カスタムフックの戻り値の関数やオブジェクトにuseMemoあるいはuseCallbackを適用する理由も上記と同様で、戻り値がPropsとして利用される場合を考慮するためです。

カスタムフックの詳細解説は【React】カスタムフックの概要・メリット・使いどころで紹介しています。

useEffectの依存配列に関数が定義されている場合はレンダリング毎の関数再作成に注意が必要

依存配列とはuseEffectの第2引数に定義する配列のことです。
依存配列を設定することで、依存配列が変更した時のみuseEffectが実行されます。

useEffectの依存配列にコンポーネントで定義した関数を指定している場合は注意が必要です。
なぜなら先ほど説明した通り、関数コンポーネント内で定義された関数やレンダリングのたびに再作成されるためです。

ですので、useCallbackは『useEffectの依存配列に定義されている関数ならば適用する。』という結論になります。

useEffectの依存配列の詳細解説はパフォーマンス改善やバグ防止に理解必須。useEffect第2引数の利用パターン集で紹介しています。

メモ化はコンポーネントだけでなく関数実行にも有効

useMemoは、主にReact.memoによるコンポーネントのメモ化の際に組み合わせて使います。

しかし、関数の処理コストが原因でパフォーマンスが低下するケースにおいてもuseMemoの活用は有効です。処理コストの高い計算結果をuseMemoを利用してメモ化することでパフォーマンスの向上が期待できます。

ですので、useMemoは『結果の算出コストの高いデータ(オブジェクト)ならば「必要に応じて」適用する。』という結論になります。

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考資料


  1. useMemoでもコンポーネントのメモ化は可能ですが、簡単のために今回は説明を省略します。