[初心者] React の 「useCallback」について初心者向け解説！

# はじめに

Reactでアプリケーションを開発するとき、**パフォーマンスの最適化**は重要な課題です！
その中でも`useCallback`は初心者にとって理解が難しい概念の一つではないでしょうか？ 
「`useState`はよく使うからなんとなく分かるんだけど、`useCallback`ってなんなの？」となっているReact初学者の方結構いるのではないでしょうか？

この記事では、`useCallback`の基本から実践的な使い方まで、初心者にもわかりやすく解説していきます！

# `useCallback`って？

`useCallback`はReactのフックの一つで、コンポーネントの再レンダリング間で関数をメモ化（キャッシュ）するために使用されます。

```jsx
const memoizedCallback = useCallback(() => {
  // コールバック関数の内容
}, [依存配列]);
```

- **第一引数**: メモ化したい関数
- **第二引数**: 依存配列（この配列の値が変わった時だけ関数が再生成される）

## メモ化ってなんだ？
メモ化は簡単に言うと、 **一度計算した結果を覚えておいて、同じ計算をするときに前回の結果を使い回すこと** です！

日常生活で例えると以下のような感じです！

### 計算機を使わない例
123 × 456 = ? を毎回手で計算する（時間がかかる）
### メモ化を使った例
最初に123 × 456 = 56,088 と計算して、メモに書いておく
次に同じ計算が必要になったら、メモを見て「56,088」と答える（速い！:bullettrain_side:）

## なぜ`useCallback`が必要？

Reactのコンポーネントが再レンダリングされるたびに、コンポーネント内で定義されている関数は新しく作り直されます。これは通常問題ないのですが、以下のような場合に問題となることがあります:frowning2:

1. パフォーマンスに敏感なコンポーネントに関数をpropsとして渡す場合
2. `useEffect`などの依存配列に関数を含める場合
3. 関数に内部状態がある場合（例：debounce関数やthrottle関数）

```txt
debounce： 最後の実行から一定時間経過後に1回だけ実行する
throttle： 一定時間内に最大1回だけ実行する
```

## `useCallback`の使用例と使い時
### `useEffect`の依存配列で関数を使う場合

```jsx
const ChatRoom = ({ roomId }) => {
  const [message, setMessage] = useState('');

  // この関数はコンポーネントが再レンダリングされるたびに新しく作られる
  const createOptions = () => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId
    };
  };

  // createOptionsが依存配列にあると、再レンダリングのたびにEffectが実行される
  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // 問題: この依存関係は毎回のレンダリングで変化する
}
```

この問題を解決するには、`useCallback`を使って関数をメモ化します！

```jsx
const ChatRoom = ({ roomId }) => {
  const [message, setMessage] = useState('');

  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId
    };
  }, [roomId]); // roomIdが変わったときだけ関数が再生成される

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]);
}
```

さらに良い方法として、関数を`useEffect`の中に移動する方法もあります！

```jsx
const ChatRoom = ({ roomId }) => {
  const [message, setMessage] = useState('');

  useEffect(() => {
    // 関数をEffect内に移動
    const createOptions = () => {
      return {
        serverUrl: 'https://localhost:1234',
        roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // roomIdが変わったときだけEffectが再実行される
}
```

## `useCallback`の注意点と落とし穴

### 1. メモ化自体にコストがかかる

`useCallback`は便利なんですが、メモ化自体にもコストがかかります:disappointed_relieved:
小さな関数や、再レンダリングが頻繁に発生しないコンポーネントでは、`useCallback`を使用する利点が少ない場合があります！

### 2. 依存配列の正しい指定

依存配列には、関数内で使用されるすべての値を含める必要があります。依存配列が不完全だとバグの原因になります！

```jsx
// 誤った使用例
const handleSubmit = useCallback(() => {
  console.log(name); // nameは依存配列に含まれていない
}, []); // バグ: nameが変わっても関数は更新されない

// 正しい使用例
const handleSubmit = useCallback(() => {
  console.log(name);
}, [name]); // 正しい: nameが依存配列に含まれている
```

### 3. 過剰な最適化を避ける

すべての関数を`useCallback`でラップすることは、コードを複雑にし、場合によってはパフォーマンスを悪化させることがあります！
パフォーマンスの問題が実際に発生した場合にのみ最適化を検討するのが良いアプローチかな？と思います！

## `useCallback`と`useMemo`の違い

`useCallback`と`useMemo`ってちょと似ていますが、目的が異なります！

- `useCallback(fn, deps)` は**関数**をメモ化します
- `useMemo(() => value, deps)` は**値（計算結果）** をメモ化します

簡単に言えば

```jsx
// 関数自体をメモ化
const memoizedFn = useCallback(() => doSomething(a, b), [a, b]);

// 関数の結果をメモ化
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```

実際には、`useCallback(fn, deps)`は`useMemo(() => fn, deps)`と同等です！

## まとめ

Reactの最適化ツールは強力ですが、「早すぎる最適化は諸悪の根源」という原則を忘れないようにしましょう！
まずはコードを明確に保ち、実際にパフォーマンスの問題が発生した場合にのみ最適化を検討することをお勧めします！

## 参考リソース

- [React公式ドキュメント - useCallback](https://react.dev/reference/react/useCallback)
- [useCallbackのユースケース3選](https://zenn.dev/frontendflat/articles/react-usecallback-usage)