useCallbackとは?Reactの再レンダリング地獄を防ぐメモ化フックの使いどころ

目次

「子コンポーネントが毎回再レンダリングされるんだけど…」

Reactで開発していて、React DevToolsのプロファイラを開いたら子コンポーネントが毎回光ってる。React.memoで囲んだのに効いてない。「なんで?」と調べてみたら、親から渡しているコールバック関数が毎回新しいインスタンスになっているのが原因だった。こんな経験、ありませんか?

この問題を解決するのがuseCallbackです。本記事では、useCallbackの基本概念から、実際に効果があるケース・ないケース、useMemoとの違いまでをコード例付きで解説します。

useCallbackとは? ― 関数をメモ化するReactフック

useCallbackは、関数の参照を依存配列が変わるまで保持する(メモ化する)Reactフックです。

Reactのコンポーネントは、stateやpropsが変わるたびに再レンダリングされます。その際、コンポーネント内で定義された関数は毎回新しいインスタンスとして生成されます。

【useCallbackなし】
レンダリング1回目: handleClick = function A(新規生成)
レンダリング2回目: handleClick = function B(新規生成 ← Aとは別物)
レンダリング3回目: handleClick = function C(新規生成 ← またまた別物)

【useCallbackあり】
レンダリング1回目: handleClick = function A(新規生成)
レンダリング2回目: handleClick = function A(同じ参照を再利用)
レンダリング3回目: handleClick = function A(同じ参照を再利用)

関数の中身は同じでも、JavaScriptでは参照が違えば別物と判定されます。これがReact.memoをすり抜ける原因です。

なぜ必要? ― 再レンダリングで関数が作り直される問題

具体的なコードで問題を見てみましょう。

// ❌ useCallbackなし:子が毎回再レンダリングされる
function Parent() {
  const [count, setCount] = useState(0);

  // レンダリングのたびに新しい関数が生成される
  const handleClick = () => {
    console.log('clicked');
  };

  return (
    <>
      

{count}

{/* handleClickが毎回新しい → React.memoが効かない */} ); } const ExpensiveChild = React.memo(({ onClick }: { onClick: () => void }) => { console.log('ExpensiveChild rendered'); // ← 毎回ログが出る return ; });

親のcountが変わるたびにhandleClickが新しく生成され、ExpensiveChildは「propsが変わった」と判断して再レンダリングされます。React.memoで囲んでいるのに意味がない状態です。

基本的な使い方

上のコードをuseCallbackで修正します。

// ✅ useCallbackあり:子の不要な再レンダリングを防ぐ
function Parent() {
  const [count, setCount] = useState(0);

  // 依存配列が空 → 初回レンダリング時の関数を保持し続ける
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return (
    <>
      

{count}

{/* handleClickの参照が変わらない → React.memoが効く */} ); }

構文はuseCallback(関数, 依存配列)。依存配列の値が変わらない限り、同じ関数参照が返されます。

依存配列にstateを含めるケース

関数内でstateを参照している場合は、依存配列に含めます。

const [query, setQuery] = useState('');

// queryが変わった時だけ関数を再生成
const handleSearch = useCallback(() => {
  fetchResults(query);
}, [query]);

依存配列を空にしたまま中でstateを使うと、古い値を参照し続けるバグ(stale closure)になるので注意。公式ドキュメント(useCallback – React公式リファレンス)も確認しておきましょう。

useCallbackが効くケース・効かないケース

useCallbackは万能ではありません。使うべき場面と、使っても意味がない場面をはっきり区別しましょう。

場面 useCallback 理由
React.memoの子にコールバックを渡す ✅ 効く 参照が安定するのでmemoが正しく動作する
useEffectの依存配列に関数を入れる ✅ 効く 参照が安定するので不要な再実行を防げる
子に渡さないローカル関数 ❌ 不要 メモ化のコスト分むしろ遅くなる
React.memoなしの子にコールバックを渡す ❌ 不要 子がそもそも毎回再レンダリングされるので意味なし

ポイントは、useCallbackは単体では何もしないということ。React.memouseEffectセットで使って初めて効果がある仕組みです。「とりあえず全部useCallbackで囲む」は、コードが読みにくくなるだけで逆効果です。

useMemoとの違い

useCallbackuseMemoは混同されがちですが、メモ化する対象が違います。

// useCallback → 「関数そのもの」をメモ化
const handleClick = useCallback(() => {
  doSomething();
}, []);

// useMemo → 「関数の実行結果(値)」をメモ化
const sortedList = useMemo(() => {
  return heavySort(items);
}, [items]);

// 実は useCallback(fn, deps) は useMemo(() => fn, deps) と同じ
フック メモ化する対象 主な用途
useCallback 関数の参照 子コンポーネントへのコールバック渡し
useMemo 計算結果の値 重い計算のキャッシュ、オブジェクト参照の安定化

迷ったら「渡すのが関数ならuseCallback、値ならuseMemo」と覚えておけばOKです。

useCallbackの理解をさらに深める関連記事

useCallbackを正しく使いこなすには、Reactのレンダリング最適化やフック全般の理解も欠かせません。

React開発の基礎力を上げる

開発環境・ツール

まとめ:useCallbackは「セットで使って初めて効く」フック

本記事のポイントを整理します。

  • useCallbackとは、関数の参照を依存配列が変わるまでメモ化するReactフック
  • 必要な理由:コンポーネント再レンダリング時に関数が毎回新しく生成され、React.memoが効かなくなる問題を解決する
  • 効く場面React.memoの子にコールバックを渡す時、useEffectの依存配列に関数を入れる時
  • 効かない場面:子に渡さないローカル関数、React.memoなしの子へのコールバック
  • useMemoとの違い:useCallbackは「関数」を、useMemoは「値」をメモ化する
  • 依存配列に注意:stateを使う関数は依存配列に含めないとstale closureバグになる

useCallbackは、理解せずに使うと「おまじない」になりがちなフックです。でも仕組みを知れば判断は明快で、「React.memoとセットで使う」「useEffectの依存に入れる関数に使う」、それ以外は不要。このルールだけ覚えておけば、パフォーマンス最適化で迷うことは減るはずです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次