useCallbackはReactフックであり、依存関係が変更されるまでレンダリング間で変更されないメモ化された関数のバージョンを返します。コンポーネント内の通常の関数宣言(レンダリングごとに新しい関数を作成する)とは異なり、useCallbackは関数の参照を安定化し、React.memoで最適化された子コンポーネントの不要な再レンダリングを防ぎます。React Documentation (2025)によると、useCallbackはReact.memoまたは安定した参照に依存するフックとの組み合わせでのみ有用です。
重要なポイント
useCallbackはReact 16.8で追加されたフックで、関数をメモ化します。依存関係が変更されるまで同じ参照を返します。useCallbackがない場合、コンポーネント内の関数宣言はレンダリングごとに新しい関数オブジェクトを作成します。プリミティブではこれは気づかれませんが、React.memoで最適化された子コンポーネントにこれらのコールバックを渡すと、新しい参照ごとに子コンポーネントの再レンダリングが発生します。
構文的には、useCallbackは関数に対するuseMemoと同等です。useCallback(fn, deps)はuseMemo(() => fn, deps)の省略形です。Reactはメモ化された関数をファイバーノードの内部ストレージに保持し、レンダリングごとに依存関係を比較します。依存関係が変更されていない場合(各アイテムに対してObject.is)、以前の関数が返されます。
React Documentation — useCallback (2025)によると、すべての関数をuseCallbackでラップすべきではありません。フックにはコストがあります:フックの呼び出し、依存関係の比較、依存関係配列のメモリ割り当てです。コンポーネントがシンプルでReact.memoを使った深いツリーがない場合、useCallbackはアプリケーションを遅くするだけです。最適化は測定可能であるべきで、直感的であってはなりません。
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// 安定した参照 — depsが変更されるまで同じ関数
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
useCallbackにおけるメモ化は、関数呼び出しの結果をキャッシュすることに基づいています。Reactは最初のレンダリング時に作成されたクロージャを保持し、依存関係が変更されない限り後続のレンダリングでそれを返します。ファイバーノード内部では、useCallback呼び出しごとにフックのリンクリストにノードが作成され、以前の依存関係とメモ化された値が格納されます。
依存関係の比較は厳密にObject.isを介して行われます — オブジェクトや配列の深いチェックを行わない浅い比較です。依存関係にオブジェクトや配列がある場合、レンダリングごとの新しい参照は変更と見なされます。したがって、依存関係配列にはプリミティブ値または安定した参照(useRefやuseMemoからのものなど)を含める必要があります。
React Core Team — Optimization Guide (2024)によると、メモ化のコストには3つの要素が含まれます:レンダリングごとの依存関係配列の割り当て、Object.isによる要素の反復と比較、および再作成時のガベージコレクションのオーバーヘッドです。何百ものuseCallbackラッパーを持つコンポーネントでは、これが顕著になる可能性があります — そのため、フックの使用における選択性が重要です。
| シナリオ | useCallbackなし | useCallbackあり |
|---|---|---|
| 関数作成 | レンダリングごとに新規 | 安定したdepsで同じ |
| React.memoに渡す | 子が再レンダリング | 子は再レンダリングしない |
| useEffect配列内 | エフェクトが再起動 | エフェクトは安定 |
| オーバーヘッド | 最小 | 依存関係の比較 + メモリ |
useCallbackが自動的にパフォーマンスを向上させるという広く信じられている誤解があります。実際には、単独では(React.memoなしで)、useCallbackは依存関係の比較コストのためにアプリケーションをわずかに遅くします。このフックは3つのシナリオでのみ実際の利益をもたらします:React.memoコンポーネントの再レンダリング防止、useEffect内のコールバックの安定化、および参照の同一性に依存するカスタムフックへのコールバックの受け渡しです。
ルールはシンプルです:React DevTools Profilerでパフォーマンス問題を検出するまでは、useCallbackを使用しないでください。Reactチームは、時期尚早な最適化は諸悪の根源であると繰り返し強調しています。まずメモ化なしでクリーンなコードを書き、測定し、プロファイラーでボトルネックを見つけてから、本当に必要な場所にuseCallbackを追加してください。
// 測定可能な最適化:ChildはReact.memoでラップされています
const Child = React.memo(({ onClick }) => {
console.log('Childが再レンダリングされました');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('クリックされました');
}, []);
return <Child onClick={handleClick} />;
}
Dan Abramov — Before You memo() (2024)によると、オープンソースプロジェクトにおけるuseCallbackの使用例の90%以上は冗長です。開発者は効果を測定せずに「念のため」すべての関数をラップします。代替案:子コンポーネントが重く、その再レンダリングが高価な場合 — React.memo + useCallbackは正当化されます。子コンポーネントが軽量な場合 — 再レンダリングは依存関係の比較よりも安価です。
最初のシナリオはReact.memoです。子コンポーネントがReact.memoでラップされ、プロパティとしてコールバック関数を受け取る場合、useCallbackがないと子コンポーネントは親のレンダリングごとに再レンダリングされ、自身のデータが変更されていなくても同様です。useCallbackは参照を安定化し、React.memoが再レンダリングを正しくスキップできるようにします。
2番目のシナリオは依存関係にコールバックがあるuseEffectです。関数がuseEffectの依存関係配列に渡される場合、新しい参照ごとにエフェクトが再起動します。useCallbackは参照が安定していることを保証し、エフェクトは実際のデータが変更された場合にのみ実行され、レンダリングごとには実行されません。これはサブスクリプションやリクエストで特に重要です。
// 安定したuseEffect依存関係のためのuseCallback
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // 安定した参照、再作成されない
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // エフェクトはprops.idが変更されたときにのみ実行
useCallbackとuseMemoの主な違いは、それぞれが何をメモ化するかです。useCallbackは関数をメモ化します:useCallback(fn, deps)はfn(同じまたは以前のバージョン)を返します。useMemoは関数呼び出しの結果をメモ化します:useMemo(() => computeExpensive(a, b), [a, b])は計算された値を返し、関数は返しません。
技術的には、useCallbackはuseMemoのシンタックスシュガーです:useCallback(fn, deps)はuseMemo(() => fn, deps)と同等です。この構文は読みやすさのためだけに存在します — 開発者が値ではなく関数がメモ化されていることを明確に確認できるようにするためです。useCallbackと関数を使用したuseMemoの間にパフォーマンスの違いはありません — 両方とも同一のコードを生成します。
| フック | メモ化するもの | 構文 | 使用法 |
|---|---|---|---|
| useCallback | 関数(参照) | useCallback(fn, deps) | 子コンポーネント用のコールバック |
| useMemo | 計算結果 | useMemo(() => value, deps) | 高価な計算、オブジェクトのメモ化 |
// これらは同等です:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
最も一般的な間違いは、子コンポーネントにReact.memoがない状態ですべての関数をuseCallbackで無意味にラップすることです。子コンポーネントがReact.memoでラップされていない場合、コールバックの参照が変更されるかどうかに関係なく、親のレンダリングごとに再レンダリングされます。React.memoなしのuseCallbackは、利益のないコストです。
// ❌ 無駄:子にReact.memoがない
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // ChildはReact.memoなしで依然として再レンダリング
// ❌ 古いクロージャ:依存関係が不足
const handle = useCallback(() => {
console.log(count); // countは常に0 — 古いクロージャ!
}, []);
// ✅ 正しい:依存関係を含める
const handle = useCallback(() => {
console.log(count);
}, [count]);
useCallbackでのstale closureの問題は、使用されているすべての変数を依存関係配列に含めることで解決されます。exhaustive-depsを使用したeslint-plugin-react-hooksは、コールバック本体のすべての変数が配列に存在することを自動的にチェックします。コールバックがレンダリング間で変更されないsetStateを使用する場合、それを安全にdepsに含めることができます — ReactはsetStateの安定性を保証します。
よくある質問
いいえ。useCallbackは3つの場合にのみ意味があります:子コンポーネントがReact.memoでラップされている、関数がuseEffectの依存関係配列で使用されている、または関数が参照の同一性に依存するカスタムフックに渡される場合です。それ以外の場合、useCallbackは利益なくオーバーヘッドを追加します。Reactチームは、まず最適化なしで記述し、プロファイリング結果に基づいて追加することを推奨しています。
シンプルなコンポーネントの場合 — レンダリングごとの新しい関数が少し速いです。useCallbackは依存関係の比較と配列の割り当てにリソースを消費するからです。深いReact.memoツリーを持つコンポーネントの場合、useCallbackが数千の子要素の再レンダリングを防いで勝ります。推測する代わりに測定して比較してください — 客観的な評価にはReact DevTools Profilerを使用してください。
はい、useCallbackはasync関数でも同期関数とまったく同じように動作します。フックは関数自体をメモ化し、結果(Promise)は呼び出されるたびに返されます。useCallback内の非同期関数は、useEffectで使用される安定したデータ取得コールバックの一般的なパターンです:const fetchData = useCallback(async (id) => {...}, [])。
React DevTools Profilerを使用してください — どのコンポーネントがなぜ再レンダリングされるかを表示します。プログラムによる確認には、console.logを追加するか、useWhyDidYouUpdate(再レンダリングの原因をログ出力するライブラリ)を使用してください。主な原因:プロパティの変更(コールバック参照を含む)、状態の変更、またはコンテキストの変更です。useCallbackが効果がない場合 — すべての依存関係が正しく指定されているか確認してください。
React.memoがなくても、useCallbackはコンテキスト値に対してuseMemoと組み合わせて有用です。Context.Providerに関数を持つオブジェクトを渡す場合、オブジェクトの作成をuseMemoで、各関数をuseCallbackでラップしてください。これにより、1つの関数が変更されたときにすべてのコンテキストコンシューマーの再レンダリングを防げます。しかし、React.memoなしでプロパティに直接コールバックを渡す場合、useCallbackの利益はありません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。