🐥

(React) useCallbackについてまとめてみました。

に公開

useCallback

React の Hook の一つです。
deps(依存配列)内の値が変わらない限り、同一の関数参照を返します。

const 関数名 = useCallback(
  () => {
    // 実行したい処理
  },
  [dep1, dep2,]  // これらの値が変わったときだけ新しく関数を作成
);

役割(スタンプ例)

  • useCallback あり → いちど作った「スタンプ」をそのまま使って押せる
  • useCallback なし → 押すたびにゴムから削って新しいスタンプを作り直すイメージ

いつ使う?

  1. React.memo した子コンポーネントへのハンドラを安定させたいとき
  2. useEffect の依存配列に関数を渡すとき
  3. カスタムフック(例: useInterval)でコールバックの一致を保ちたいとき

例:1. React.memo と組み合わせた子コンポーネントの最適化

import React, { useState, useCallback } from 'react';

const Child = React.memo(({ onClick }: { onClick: () => void }) => {
  console.log('👶 Child がレンダーされました');
  return <button onClick={onClick}>子ボタン</button>;
});

export function Parent() {
  const [count, setCount] = useState(0);

  // deps=[] → 初回のみ生成。以降は同じ関数参照を使い回す
  const handleClick = useCallback(() => {
    console.log('👉 子ボタンがクリックされました');
  }, []);

  return (
    <div>
      <p>Parent count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Parent +1</button>
      <Child onClick={handleClick} />
    </div>
  );
}

挙動

  • 初回レンダー時

    👶 Child がレンダーされました
    
  • 「Parent +1」クリック → Parent は再レンダーされるが

    • Child は再レンダーされず(コンソール出力なし)
  • 「子ボタン」クリック

    👉 子ボタンがクリックされました
    

例:2. useEffect の依存配列に関数を渡すパターン

import React, { useState, useCallback, useEffect } from 'react';

export function ExampleEffect() {
  const [value, setValue] = useState(0);

  // value が変わるたびに新しい compute が生成される
  const compute = useCallback(() => {
    console.log('🔄 compute を実行:', value);
  }, [value]);

  // compute が変わる(=value が変わる)たびに effect を実行
  useEffect(() => {
    compute();
  }, [compute]);

  return (
    <button onClick={() => setValue(v => v + 1)}>
      value: {value}
    </button>
  );
}

挙動

  • 初回レンダー時

    🔄 compute を実行: 0
    
  • 「value」ボタンクリック → value が 1 に更新 →

    🔄 compute を実行: 1
    
  • 以降クリックする度に、その時点の value をログ出力


例:3. カスタムフック useInterval と組み合わせる例

import React, { useState, useCallback, useEffect, useRef } from 'react';

// useInterval: コールバックを ref で保持しつつ、delay ごとに呼び出す
function useInterval(callback: () => void, delay: number) {
  const saved = useRef(callback);
  useEffect(() => { saved.current = callback; }, [callback]);
  useEffect(() => {
    const id = setInterval(() => saved.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

export function Clock() {
  const [time, setTime] = useState(() => new Date().toLocaleTimeString());

  // deps=[] → tick の参照が変わらないので useInterval が安定動作
  const tick = useCallback(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  useInterval(tick, 1000);

  return <div>現在時刻: {time}</div>;
}

挙動

  • 初回レンダー → 現在時刻を表示
  • 以降 1 秒ごとに tick() が呼ばれtime が更新 → UI が毎秒リアルタイムに変化

まだ使うべきタイミングを見極めるのは難しいですが、その特徴をしっかり理解した上で、経験を重ねながら適切に活用していきたいと思います。

Discussion