useCallbackとは
関数をキャッシュするReactフックです。
第1引数に関数、第2引数に依存配列を指定し、第2引数に変更がある場合のみ関数が再度実行されます。
基礎的な事例などはReact公式ドキュメント(useCallback)にあります。
どこでも利用すべきか
関数をキャッシュできるならとにかく使おう。という実装方針をとることはパフォーマンスや運用の観点で有効とは言えません。
パフォーマンスの観点では、メモ化を実施すること自体や依存配列をチェックする作業にコストがかかるため、そのコストに見合うリターンがない場合はuseCallbackの利用価値はありません。
主に利用価値が発揮されるのは以下のような場合が考えられます。
- React.memoでラップされた重そうな描画の実施を伴うコンポーネントにpropsとして渡す場合
- 重い処理や描画を伴うuseEffectの依存配列に設定されている場合
これらと一緒に利用せず、useCallbackを単体利用することで価値が発揮されるケースはほとんどありません。
Reactの公式ドキュメント(useCallback#optimizing-a-custom-hook)にも以下のように記載されています。
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.
重い描画処理がある場合に有効であるとのことです。
ただ、Reactの公式ドキュメント(useCallback#optimizing-a-custom-hook)にも記載がありますが、パフォーマンスへの悪影響は小さいため、どこでも利用するチームは存在しそうです。
There is no benefit to wrapping a function in useCallback in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible.
しかし、運用の観点では、useCallbackの利用によりコードの量が増え、読みにくくなるという事象が発生します。
カスタムフックが関数を返すならuseCallbackを利用すべき
useCallbackについて調べている際にいくつかの記事を読みましたが、そのほとんどは上記のような利用するにもコストがかかるから重い描画処理がある場合にのみ利用すべきだという内容ばかりでした。
しかし、ある記事ではカスタムフックが関数を返す場合は常にuseCallbackを利用すべきという記述がありました。
その理由は以下のように示されていました。
カスタムフックを作る理由は、普通の関数を作る理由と全く同じであり、すなわち責務の分離とかカプセル化です。 一度カスタムフックとして分離された以上、インターフェースの内側のことはカスタムフック内で完結すべきです。 カスタムフックを使う側はカスタムフックの内側のことを知るべきではなく、その逆も然りです。
重い処理でカスタムフックを利用することになり、useCallbackを後から追加することになった場合、それは「再レンダリングを強制する」という仕様から「再レンダリングを抑制する」という仕様変更したことになります。再利用可能性と独立性が高いカスタムフックでなかったため、後から追加する必要があったということです。仕様変更に伴い他の使用箇所への影響も考慮する必要があります。そのため、最初からuseCallbackを利用することが、再利用可能性と独立性が高いカスタムフックを作るために必要と考えられます。
どのように運用すべきか
プロジェクトのコーディングルール等でuseCallbackの利用基準を明確にして運用した方が良いと思いました。途中から参画したエンジニアがuseCallbackを独自の観点で常に利用してしまうことや共通利用するカスタムフックをuseCallbackを利用せず後々修正が必要となることを防げます。また、Reactに精通していないエンジニアが多い環境でuseCallbackを単体利用してしまうことを防ぐことにもつながります。