🪝

useCallbackはどう使う?

に公開

対象読者

  • Reactのhooksについて学習中の方
  • useCallbackっていつ使うの?という方

useCallbackとは

useCallback は、関数をキャッシュ(メモ化)するためのReactフックです。

初回レンダリング時には新しい関数を生成しますが、依存配列(dependencies)に含まれる値が変わらない限り、同じ関数を返し続けます。

const cachedFn = useCallback(fn, dependencies)

// fn: キャッシュしたい関数
// dependencies: 関数コード内で参照されるリアクティブな値リスト

なぜuseCallbackが必要なのか?

Reactではコンポーネントが再レンダーされると、その子コンポーネントも再レンダーされます。
しかし、子コンポーネントを React.memo でメモ化しておくと、propsが前回と同じであれば再レンダーをスキップできます。

問題は、関数をpropsとして渡している場合です。
JavaScriptでは、次のような関数式は毎回新しい関数オブジェクトを生成します。

function () {}
// または
() => {}

そのため、props内の関数は「毎回違う値」と判断され、
React.memo でラップしていても子コンポーネントが再レンダーされてしまいます。

これを防ぐために useCallback を使うと、同一の関数参照を再利用できるようになり、
効率的にレンダリングを抑えることができます。

useCallbackを使うべきケース

公式ドキュメントには次のように述べられています。

  • それを memo でラップされたコンポーネントに props として渡すケース。この場合は、値が変化していない場合には再レンダーをスキップしたいでしょう。メモ化することで、依存値が異なる場合にのみコンポーネントを再レンダーさせることができます。
  • あなたが渡している関数が、後で何らかのフックの依存値として使用されるケース。たとえば、他の useCallback でラップされた関数がそれに依存している、または useEffect からこの関数に依存しているケースです。

つまり以下のケースで useCallback を使うことを推奨しています。

メモ化されたコンポーネントにpropsとして関数を渡すとき
→ 子コンポーネントが不要な再レンダーを避けられる。

他のフックの依存値として関数を使うとき
→ たとえば useEffect や別の useCallback がその関数を依存として使う場合。

useCallbackのデメリット

公式ドキュメントで次のように述べられています。

これらのケース以外では、関数を useCallback でラップすることにメリットはありません。それを行っても重大な害はないため、個別のケースを考えずに、可能な限りすべてをメモ化するようにしているチームもあります。このアプローチのデメリットは、コードが読みにくくなることです。また、すべてのメモ化が効果的なわけではありません。例えば、毎回変化する値が 1 つ存在するだけで、コンポーネント全体のメモ化が無意味になってしまうこともあります。

不要なメモ化は可読性を下げる
→ コードが複雑に見え、関数の意図が分かりにくくなる。

依存値がよく変わる関数は、結局毎回再生成される
→ 結果として、パフォーマンス向上にはつながらない。

参照

https://ja.react.dev/reference/react/useCallback#usage

Discussion