ReactReact [リアクト]Meta開発のUIライブラリアプリケーションを開発していると、パフォーマンス最適化の手段として必ず耳にするのが useMemo と useCallback です。
どちらも「不要な再レンダリングや無駄な再計算を減らすためのフック(メモ化フック)」ですが、実務や学習の現場では以下のような疑問を抱く人が多くいます。
- 「
useMemoとuseCallbackは何が違うのか?」 - 「どちらを使えばいいのか、使い分けの判断基準が分からない」
- 「パフォーマンス向上のために、とりあえず全部の関数や変数に付けた方がいいのか?」
結論から言うと、両者の本質的な違いは 「何をキャッシュ(メモ化)するか」 だけです。
useMemo: 関数の 実行結果(計算された値) をキャッシュするuseCallback: 関数そのもの(関数オブジェクトの参照) をキャッシュする
本記事では、この2つのフックの決定的な違い、それぞれの具体的な使い方、React.memo と組み合わせる重要性、そして「逆にパフォーマンスを落としてしまう過剰なメモ化のアンチパターン」までを分かりやすく解説します。
ひと目でわかる!useMemo と useCallback の違い比較表
まずは、両者の違いを表で整理します。
| 項目 | useMemo | useCallback |
|---|---|---|
| キャッシュ対象 | 関数の 計算結果(値) | 関数そのもの(関数オブジェクトの参照) |
| 主な用途 | CPUCPU [シーピーユー]Central Processing Unit。コンピュータの頭脳となる中央演算処理装置負荷の高い重い計算のスキップ、オブジェクト・配列の参照保持 | 子コンポーネントへ渡すコールバック関数の参照保持、useEffect の依存関係安定化、カスタムフックから返す関数の参照保持 |
| 戻り値 | 第1引数の関数が返した 値 (数値、文字列、オブジェクト、配列など) | 第1引数に渡した 関数そのもの |
| 基本構文 | useMemo(() => computeValue(a, b), [a, b]) | useCallback((e) => handleClick(e), [a, b]) |
| 等価な書き換え | - | useMemo(() => fn, deps) と実質的に等価 |
React公式ドキュメントでは、useCallback(fn, deps) は useMemo(() => fn, deps) と実質的に等価であると説明されています。useMemo が関数を実行してその戻り値をキャッシュするのに対し、useCallback は渡された関数自体をキャッシュします。
useMemo の仕組みと具体的な使い方
まずは、計算結果の値をキャッシュする useMemo について詳しく見ていきましょう。
なぜ useMemo が必要なのか?
Reactの関数コンポーネントは、自身の state の更新や親コンポーネントの再レンダリングなどが発生するたびに、関数本体が上から下まで最初から再実行(再レンダリング)されます。
function ProductList({ products, filterText }) { // コンポーネントが再レンダリングされるたびに、この重い計算が毎回実行される! const filteredProducts = slowFilter(products, filterText);
return <div>{/* 商品一覧の表示 */}</div>;}もし slowFilter が数千件・数万件のデータをループして集計・ソートするような重い処理だった場合、関係のない state(例えばページ内の検索以外の小さなモーダル開閉など)が変更されただけでも、毎回この重い計算が実行されて画面のカクつき(ジャンク)を引き起こします。
useMemo の基本構文
そこで useMemo を使うと、第2引数に指定した 依存配列の値が前回のレンダリング時から変化していなければ、Reactは原則として前回の計算結果を再利用 します。
ただし、useMemo はあくまでパフォーマンス最適化のための仕組みであり、 キャッシュの保持を永久に保証するものではありません 。開発中のファイル編集時や、初回マウント時にコンポーネントが Suspense によって中断された場合、あるいは将来的に React がキャッシュを破棄する新しい仕組みを導入した場合など、特定の理由でキャッシュが破棄されることもあるため、アプリケーションの正しい動作をメモ化のキャッシュに依存させないようにしましょう。
なお、React Hooks のルールに従い、useMemo はコンポーネントまたはカスタムフックのトップレベルで呼び出す必要があります。
import { useMemo } from "react";
function MyComponent({ a, b }) { const memoizedValue = useMemo(() => { // 重い計算処理 return computeExpensiveValue(a, b); }, [a, b]); // a または b が変化したときに再計算される
return <div>{memoizedValue}</div>;}[!NOTE] Strict Mode に関する注意点 Reactの開発環境で Strict Mode が有効になっている場合、
useMemoに渡した計算関数は初回レンダリング時などに 意図的に2回呼び出される ことがあります。これは計算処理が純粋であることを確認するための開発時のチェックであり、本番環境では発生しません。そのため、開発環境で計算関数内のログが2回出力されても、必ずしもメモ化が失敗しているわけではありません。
useMemo の実践例(重いフィルタリング処理のスキップ)
以下は、数千件のアイテムからキーワード検索を行う実践的なコード例です。
import { useState, useMemo } from "react";
// わざと負荷をかけた重いフィルタリング関数の例function slowFilterItems(items, query) { console.log("重いフィルタリング処理を実行中..."); const startTime = performance.now(); while (performance.now() - startTime < 100) { // 意図的に100msの遅延を発生させて重い計算を再現 } return items.filter((item) => item.name.toLowerCase().includes(query.toLowerCase()) );}
export function ItemSearch({ items }) { const [query, setQuery] = useState(""); const [count, setCount] = useState(0);
// query または items が変わったときだけ slowFilterItems を実行する const filteredItems = useMemo(() => { return slowFilterItems(items, query); }, [items, query]);
return ( <div> <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="アイテムを検索..." /> {/* カウントボタンを押しても filteredItems の重い再計算は走らない */} <button onClick={() => setCount((prev) => prev + 1)}> 無関係なカウンター: {count} </button>
<ul> {filteredItems.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> </div> );}上記のコードでは、ユーザーが「無関係なカウンター」ボタンを何度クリックしても、query と items が変わっていないため slowFilterItems はスキップされ、前回のキャッシュ値が再利用されます。
※このコードは useMemo の動作を視覚的に確認するためのデモ用コードとして、意図的に100msの処理時間とログ出力を含めています。実際のアプリケーションでは、データ量の多いフィルタリングやソートなど実測で時間がかかる処理を対象とし、 計算関数は純粋に保ち、ログ出力や時間経過に依存する処理などの副作用を含めない ようにしてください。
useCallback の仕組みと具体的な使い方
次に、関数の参照をキャッシュする useCallback について見ていきましょう。
なぜ useCallback が必要なのか?(関数の参照等価性)
JavaScriptでは、関数は オブジェクトの一種 として扱われます。プリミティブ値(数値や文字列)とは異なり、関数やオブジェクトを新しく生成すると、それぞれ 異なる参照 を持つ値になります(値と参照の基本については JavaScriptのvar・let・constの違い や スタック領域とヒープ領域の違い も参照してください)。
そのため、処理内容がまったく同じ関数であっても、別々に生成された関数同士を === で比較すると false になります。以下の検証コードを見てください。
// 見た目は全く同じ関数でも、新しく作成されると参照が異なるconst fnA = () => console.log("hello");const fnB = () => console.log("hello");
console.log(fnA === fnB); // falseReactのコンポーネント内にインラインで関数を書くと、 レンダリングのたびに関数が新しく生成 されます。
function Parent() { const [count, setCount] = useState(0);
// Parent が再レンダリングされるたびに、handleClick は「全く新しい関数」として再生成される const handleClick = () => { console.log("クリックされました"); };
return <Child onClick={handleClick} />;}通常の HTMLHTML [エイチティーエムエル]HyperText Markup Language。Webページの構造を記述する言語 要素(<button> など)に関数を渡すだけであれば、関数が再生成されてもブラウザにとって大きな問題はありません。しかし、 子コンポーネントへ関数を props として渡す場合 や、 useEffect の依存配列 に関数を渡す場合に、不要な再実行や再描画の問題が発生します。
useCallback の基本構文
useCallback は、依存配列の値が変化しない限り、 前回のレンダリング時に返された関数と同じ参照 を返します(useMemo と同様、キャッシュの保持を絶対的に保証するものではなく、パフォーマンス最適化のための仕組みです)。
なお、コンポーネントの再レンダリング時には、第1引数に記述した関数式自体は評価されます。useCallback は 関数の生成そのものを防ぐのではなく、Reactが返す関数の参照をキャッシュする仕組み です。
import { useCallback } from "react";
function MyComponent({ a, b }) { const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); // a または b が変化しない限り、前回の関数の参照を再利用する
return <Child onAction={memoizedCallback} />;}React.memo との組み合わせ(子コンポーネントの無駄な再レンダリング防止)
useCallback が効果を発揮する代表的な場面は、 React.memo でメモ化された子コンポーネント に対話用のコールバック関数を props として渡すときです。
通常、親コンポーネントが再レンダリングされると、その配下にある子コンポーネントも再レンダリングの対象になります。
ただし、子コンポーネントを React.memo でラップし、props に変更がなければ、React は子コンポーネントの再レンダリングをスキップできます(なお、React.memo は再レンダリングを必ず防ぐ保証ではなく、パフォーマンス最適化のための仕組みです。また、子コンポーネント自身の state や参照している Context が変化した場合は再レンダリングされます)。
その際、親から渡される関数やオブジェクトが毎回新しい参照になっていると、props が変更されたと判定され、メモ化の効果が得られなくなる場合があります。
import { useState, useCallback, memo } from "react";
// 子コンポーネントを React.memo でラップしてメモ化const ExpensiveChild = memo(function ExpensiveChild({ onReset }) { console.log("ExpensiveChild がレンダリングされました"); return <button onClick={onReset}>リセット</button>;});
export function ParentDashboard() { const [count, setCount] = useState(0); const [text, setText] = useState("");
// useCallback で返される関数の参照をキャッシュする const handleReset = useCallback(() => { setText(""); }, []); // 依存配列が空なので、再レンダリング時も原則として前回の関数参照が返される
return ( <div> <input value={text} onChange={(e) => setText(e.target.value)} /> <button onClick={() => setCount((c) => c + 1)}>カウント: {count}</button>
{/* handleReset の参照が変わらないため、count を変更しても ExpensiveChild の再描画をスキップできる */} <ExpensiveChild onReset={handleReset} /> </div> );}もしここで handleReset に useCallback を使わずに普通の関数として定義していた場合、親コンポーネントで count を増やすたびに ExpensiveChild も毎回再レンダリングされてしまいます。
[!IMPORTANT] 子コンポーネントの再レンダリング防止を目的として
useCallbackを使う場合、 受け取り側の子コンポーネントがReact.memo等でメモ化されていること が前提になります。メモ化されていない通常の子コンポーネントに関数を渡すだけであれば、useCallbackを使っても子コンポーネントの再レンダリングはスキップされません。
実践!useMemo と useCallback の使い分けフローチャート
どちらを使うべきか迷ったときは、以下の判断フローに従ってください。
または依存配列の参照等価性が必要か?"} Q1 -->|"「関数」そのもの(イベントハンドラ等)"| Q3{"その関数の参照を安定させる必要があるか?"} Q2 -->|Yes| A1["useMemo を使用する"] Q2 -->|No: 単純な足し算や文字列結合| A2["メモ化は不要(通常の変数)"] Q3 -->|"React.memo化した子コンポーネント
useEffectの依存関係から関数を除去できない
カスタムフックの戻り値"| A3["useCallback を使用する"] Q3 -->|"通常のHTMLタグやメモ化していない子"| A4["基本的にメモ化は不要(インライン関数)"]
使い分けの典型的なシチュエーション
1. useMemo を使うべき場面
- 数千件のリストのソート、フィルタリング、複雑なデータ変換
- 子コンポーネントに渡すオブジェクトや配列の参照を安定させたいとき
- カスタムフックの戻り値としてオブジェクトを返すとき (消費側の
useEffect依存配列を安定させるため)
2. useCallback を使うべき場面
React.memoでラップされた子コンポーネントに関数を props として渡すときuseEffectや別のフックの依存配列に関数を渡す必要があるとき- カスタムフックから返す関数の参照を安定させ、利用側のコンポーネントがメモ化しやすいようにしたいとき
// カスタムフックから返す関数を useCallback でラップする例function useRouter() { const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => { dispatch({ type: "navigate", url }); }, [dispatch]);
return { navigate };}補足:useEffect 内で使う関数は「エフェクト内部への移動」も検討する
なお、useEffect の依存配列に関数を指定する場合、 必ずしも useCallback が必要とは限りません 。エフェクト内でしか使用しない関数であれば、useEffect の内部に直接関数を定義することで、useCallback を使わずに依存関係をシンプルにできる場合があります。
// ❌ useCallback で関数の参照を固定して依存配列に渡す例(やや冗長)function ChatRoom({ roomId }) { const createOptions = useCallback(() => { return { serverUrl: "https://localhost:1234", roomId, }; }, [roomId]);
useEffect(() => { const options = createOptions(); const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [createOptions]);}
// ⭕ useEffect の内部に関数を定義する例(useCallback が不要でシンプル)function ChatRoom({ roomId }) { useEffect(() => { function createOptions() { return { serverUrl: "https://localhost:1234", roomId, }; }
const options = createOptions(); const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]);}やってはいけない!メモ化のアンチパターンと注意点
「メモ化すればするほどアプリが速くなる」と勘違いしがちですが、実際には メモ化自体にも管理コストが存在します 。不要なメモ化はコードの複雑化や可読性の低下を招くため、避けるべきパターンを押さえておきましょう。
アンチパターン1: 単純な計算をすべて useMemo で囲む
以下のような単純な計算に useMemo を使うのは代表的なアンチパターンです。
// ❌ やってはいけない例:過剰なメモ化const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);const totalPrice = useMemo(() => price * quantity, [price, quantity]);
// ⭕ 正しい例:単なる計算としてそのまま書くconst fullName = `${firstName} ${lastName}`;const totalPrice = price * quantity;文字列の結合や四則演算のような非常に軽い処理では、メモ化によって得られる パフォーマンス上のメリットはほとんどありません 。
一方で、依存配列の管理や比較(Object.is)などの処理が追加されるため、不要なメモ化はコードの複雑化や可読性の低下につながります。計算処理が十分に軽い場合は、通常の変数として計算する方がシンプルです。
アンチパターン2: メモ化していないコンポーネントへの useCallback
React.memo などでメモ化されていない子コンポーネントや、通常の <button> 要素に関数を渡すだけであれば、一般的に useCallback による参照の固定は必要ありません。
// ❌ 意味のない最適化const handleClick = useCallback(() => { setCount((c) => c + 1);}, []);
// 通常の HTML 要素(<button>)や React.memo されていない子コンポーネントでは、// 関数の参照を固定しても再レンダリング防止の効果はないreturn <button onClick={handleClick}>クリック</button>;この場合、useCallback の有無にかかわらず <button> の描画処理や通常の子コンポーネントの再レンダリングには影響しません。ただし、関数が別のフックの依存配列に含まれている場合や、カスタムフックの戻り値として関数を公開する場合などは、参照を安定させる意味があります。
アンチパターン3: 依存配列の指定漏れによる Stale State(古い値の参照)
メモ化フックで代表的なバグの原因となるのが、 依存配列の指定漏れ です。
function Timer() { const [seconds, setSeconds] = useState(0);
// ❌ 依存配列に seconds がないため、初期値 0 を掴み続けてしまう const logSeconds = useCallback(() => { console.log(`現在の秒数: ${seconds}`); }, []); // 警告: React Hook useCallback has a missing dependency: 'seconds'
return ( <div> <button onClick={() => setSeconds((s) => s + 1)}>秒数を進める</button> <button onClick={logSeconds}>秒数をログ出力</button> </div> );}「秒数を進める」ボタンでコンポーネントが再レンダリングされて seconds が増えても、useCallback のキャッシュが更新されないため、logSeconds は初回作成時の seconds = 0 のクロージャを参照し続けます。これを Stale Closure(古いクロージャ) と呼びます。
必ず ESLintESLint [イーエスリント]JavaScriptやTypeScriptのコードに潜むバグや不要な記述をルールに沿って検出する静的解析ツール の公式ルール eslint-plugin-react-hooks(react-hooks/exhaustive-deps)を導入し、この例のように値を参照する場合は依存配列に [seconds] を正しく設定しましょう(なお、state の更新のみを行う関数であれば setSeconds((s) => s + 1) のような関数型更新を使うことで依存を減らせます)。
【補足】React 19 と React Compiler 時代のメモ化
現在、Reactエコシステムでは React Compiler(コードネーム: React Forget) の導入が進んでいます。
React Compiler は、ビルド時にコードを解析し、コンポーネントや値を 自動的に最適な単位でメモ化 してくれるツールです。2025年10月に安定版の v1.0 がリリースされました。React 本体とは別のビルドツール(BabelBabel [バベル]最新のJavaScriptコードを古いブラウザでも動作するコードに変換するトランスパイラ プラグイン等)として導入するもので、React 19(ランタイム組み込み済み)での利用が推奨されていますが、react-compiler-runtime パッケージを導入しコンパイラのターゲットバージョンを指定すれば React 17 / 18 でも利用できます。
React Compiler が有効化されたプロジェクトでは、開発者が手動で useMemo や useCallback、React.memo を記述する必要性は大幅に減ります。
ただし、React Compiler による最適化の対象外となるコードや、コンパイラが適切に最適化できないケースでは、引き続き手動のメモ化フックが役立つ場合があります。また、React Compiler を導入する前から手動でメモ化しているコードについては、動作やパフォーマンスを確認しながら段階的に移行することが重要です。
そのため、2026年現在の実務現場でも、以下の理由から useMemo と useCallback の知識は引き続き重要です。
- 既存の数多くのプロジェクトやライブラリコードの保守・読解に不可欠であること
- React Compiler を導入していない環境や移行中の現場が依然として多いこと
- 「何が原因で不要な再レンダリングが起きるのか」という React のレンダリング原理を理解する上で不可欠な基礎概念であること
まとめ
最後に、useMemo と useCallback の要点をまとめます。
useMemo: 計算結果の 「値」 をキャッシュする。重い集計・ソート処理のスキップや、オブジェクトの参照安定化に使うuseCallback: 返される 「関数の参照」 をキャッシュする(関数の生成自体を防ぐわけではない)。useMemo(() => fn, deps)と実質的に等価なショートハンド- キャッシュの性質 : メモ化はあくまでパフォーマンス最適化の仕組みであり、キャッシュの永久保持が保証されるものではない
- 有効な場面 :
React.memoされた子コンポーネントへの関数渡し、useEffect等の依存配列での参照安定化(エフェクト内関数で代替できない場合)、カスタムフックから返す関数の参照保持 - アンチパターン : 非常に軽い計算や、メモ化されていない要素・子コンポーネントへの不要なメモ化は可読性を損なう
- 依存配列の厳守 : ESLint の
exhaustive-depsルールに従い、古いクロージャ(Stale Closure)のバグを防ぐ
まずは「パフォーマンス上のボトルネックを React Profiler 等で計測し、本当に必要な箇所にだけ適用する」という基本姿勢を大切にしましょう。
あわせて読みたい関連記事
ReactのフックやJavaScriptのメモリ・参照の仕組みをさらに深めたい方は、以下の記事もあわせてご覧ください。

【React】useEffectの使い方!依存配列とクリーンアップ完全解説
ReactのuseEffectフックの使い方を徹底解説。基本構文から依存配列の3パターン、クリーンアップ関数の仕組み、React 18のStrict Modeでの挙動、無限ループなどの落とし穴や使わない判断基準までコード例付きで分かりやすく紹介します。

【JavaScript】var, let, constの違いと使い分けを初心者向けに徹底解説
JavaScriptの変数宣言(var, let, const)の違いを比較表とコード例で分かりやすく解説。スコープ、再宣言、再代入のルールに加え、「巻き上げ」やTDZ、よくあるバグ例まで詳しく説明します。

スタック領域とヒープ領域の違いとは?メモリ管理から使い分けまで徹底解説
「スタック領域とヒープ領域」の違いが分からず、メモリ管理に悩んでいませんか?本記事で両者の特徴、使い分け、トラブル回避法を徹底解説。高速なスタックと柔軟なヒープを理解し、スタックオーバーフローやメモリリークを防ぎ、パフォーマンスの高いコードを書くコツを掴み、効率的な開発を目指しましょう。
以上で本記事の解説を終わります。
よいITライフを!
人気記事
- 1
- 2
- 3
- 4
- 5
思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。