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 つ存在するだけで、コンポーネント全体のメモ化が無意味になってしまうこともあります。
不要なメモ化は可読性を下げる
→ コードが複雑に見え、関数の意図が分かりにくくなる。
依存値がよく変わる関数は、結局毎回再生成される
→ 結果として、パフォーマンス向上にはつながらない。
参照
Discussion