Skip to main content

第84章:【フック】useCallback


1. この章のゴール 🎯

この章では、

  • 「なんで**useCallback が出てくるのか?**」
  • 何をしてくれるフックなのか?
  • 「React 19 の世界で、どういう場面でまだ知っておくべきか?

を、かわいく(?)サクッと理解することがゴールです ✨

キーワード: **「関数の“中身”じゃなくて“同じ人かどうか(参照)」を保つ」**っていう発想 👩‍🏫


2. 復習:関数を子コンポーネントに渡すときの問題 😵

前の章で出てきたキーワードをおさらいすると:

  • React コンポーネントは 状態(state)や props が変わると再レンダー する
  • 親が再レンダーすると、JS の世界では () => { ... } みたいな**関数オブジェクトも全部「新しく作り直し」**される
  • React.memo で子コンポーネントをメモ化しても、 props に渡している関数が毎回「別のオブジェクト」だと、子も再レンダーしちゃう

この流れを図でイメージしてみます 👀

mermaid
flowchart LR
A["親の state が変わる"] --> B["親コンポーネントが再レンダー"]
B --> C["JS の世界で<br/>毎回 新しい関数 onClick を作る"]
C --> D["子コンポーネントの props(onClick) の<br/>『参照』が毎回ちがう"]
D --> E["React.memo してても<br/>子コンポーネントが再レンダー 😢"]

ここで登場するのが useCallback です ✨

「この関数、中身が同じなら前のを再利用していいよ〜」と React にお願いするフックだと思ってください。(React)


3. useCallback の基本形 🧠

公式ドキュメントでの useCallback の型はこんなイメージです:

useCallback(fn, dependencies)

  • fn: メモ化したい関数
  • dependencies: その関数の中で使っている “外側の値” の一覧(依存配列)(React)

TypeScript & React で書くと、いちばんシンプルな形はこんな感じです👇

tsx
import { useCallback, useState } from "react";

type ChildButtonProps = {
onClick: () => void;
};

function ChildButton({ onClick }: ChildButtonProps) {
console.log("ChildButton レンダー!");
return <button onClick={onClick}>子ボタンをクリック</button>;
}

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

// 👇 この関数を useCallback で「覚えておく」
const handleChildClick = useCallback(() => {
console.log("子ボタンが押されたよ!");
}, []); // ← 依存配列

return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>

<ChildButton onClick={handleChildClick} />
</div>
);
}

ここでのポイント ✍️

  • 最初のレンダー時:

    • useCallback に渡した関数がそのまま返ってくる
  • 2回目以降のレンダー:

    • 依存配列の中身が変わらなければ 「前に保存しておいた同じ関数オブジェクト」を返してくれる
  • [](空配列)の場合:

    • 「一度作ったら、基本ずっと同じ関数を使い回す」という意味

useCallback 自体は Hooks の一種なので、他のフックと同じく コンポーネントのトップレベルでだけ呼び出す ルールがあります。(React)


4. React.memo とセットで使うときのイメージ 🔗

useCallback が本領発揮するのは、メモ化された子コンポーネントに関数を渡すときです。

tsx
import { memo, useCallback, useState } from "react";

type ChildButtonProps = {
onClick: () => void;
};

// 👇 子コンポーネントを React.memo でメモ化
const ChildButton = memo(function ChildButton({ onClick }: ChildButtonProps) {
console.log("ChildButton レンダー!");
return <button onClick={onClick}>子ボタンをクリック</button>;
});

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

const handleChildClick = useCallback(() => {
console.log("子ボタンが押されたよ!");
}, []);

return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>

{/* 👇 onClick は「毎回同じ参照」なので、
count だけ変わった時は ChildButton は再レンダーされない */}
<ChildButton onClick={handleChildClick} />
</div>
);
}

この状態を図にすると、こんな感じです 🎨

mermaid
flowchart LR
A["親の state が変わる"] --> B["親コンポーネントが再レンダー"]
B --> C["useCallback により<br/>同じ onClick 関数を再利用"]
C --> D["子コンポーネントの props(onClick) の<br/>『参照』が変わらない"]
D --> E["React.memo な子は<br/>再レンダーしなくて OK 😊"]

5. 依存配列と TypeScript の関係 📚

useCallback の第2引数の 依存配列 は、かなり大事なポイントです。

tsx
const handleSave = useCallback(() => {
// ✅ ここで使っている値は、基本ぜんぶ依存配列に入れる
console.log("今の count:", count);
console.log("ユーザー名:", userName);
}, [count, userName]);
  • 関数の中で使っている state / props / そのコンポーネント内で定義された変数や関数 は、 原則として 全部依存配列に書く イメージです
  • Lint(ESLint + React Hooks ルール)を入れておくと、 「これ依存配列に書き忘れてない?」って教えてくれます 👮(React)
  • TypeScript 的には、useCallback の中で引数の型をしっかり書いておくと安心です
tsx
const handleSubmit = useCallback((value: string) => {
console.log("送信:", value);
}, []);

↑みたいに書けば、value の型もちゃんとつきます(推論でもだいたいいけますが、明示してもOK)。


6. React 19 だとどうなるの?🤔

React 19 では、コンパイラによる自動最適化 がどんどん進んでいて、

  • 「関数の参照をいい感じに安定させる」
  • 「不要な再レンダーを自動で減らす」

といった最適化が入っている&入ろうとしています。(ShareWis Blog(シェアウィズ ブログ))

その結果、

  • 昔ほど「なんでもかんでも useCallback」って書かなくてよくなる
  • シンプルに const handleClick = () => { ... } と書くだけで 十分なケースが増えてきています(DEV Community)

ただし、

  • useCallback 自体は ちゃんと生きている公式フック で、

    • 既存の React 17 / 18 プロジェクト
    • コンパイラをまだ導入していないプロジェクト
    • ライブラリ側など、「絶対に関数の参照を固定したい」場面
  • では、今でもフツーに使われています 👌(GitHub)

この講座では: 「React 19 では前より出番が減るかもだけど、 **useCallback を理解しておくと、既存コードも怖くないし、 ‘なにが最適化されてるのか’ も見抜きやすくなるよ」 というスタンスで扱っていきます ✨


7. よくあるミス&チェックリスト ✅

useCallback でよくあるパターンを、先に知っておきましょう。

❌ よくあるミス

  1. 依存配列をわざと空にする

    • 本当は count を使ってるのに、[] にしてしまう → 「ずっと昔の count を使い続けるバグ(古い値問題)」が起きる
  2. 「とりあえず全部 useCallback にしちゃおう」と乱用

    • useCallback 自体にもオーバーヘッドがあります
    • 「本当にメモ化したいか?」を考えずに全部包むと、 逆にコードが読みにくく&遅くなることも 💦(DEV Community)
  3. useCallback を if 文の中で呼ぶ

    • Hooks のルール違反(コンポーネントのトップレベルで呼ぶこと)

✅ ざっくりチェックリスト

useCallback を検討してもよさそうな場面:

  • 子コンポーネントを React.memo でメモ化している
  • その子に渡している関数が原因で「ムダな再レンダー」が多そう
  • 子コンポーネントが 重い描画(グラフ・大きなリストなど) をしている

そこまで気にしなくていい場面:

  • 画面が小さくて、子コンポーネントも軽い
  • パフォーマンス的に困っていない
  • React 19 + Compiler で、特にカクつきを感じない

※ メモ化や最適化についての「全体の考え方」は、第90章で改めて整理します 🌈


8. まとめ&次章へのつなぎ 🚀

この章で覚えておきたいポイントはこれだけです 💡

  • useCallback(fn, deps) は、関数の「参照」自体を覚えておくフック
  • React.memo な子コンポーネントに関数を渡すとき、 子のムダな再レンダーを減らすために使うことが多い
  • 依存配列には、関数の中で使う外側の値(props / state など)をちゃんと入れる
  • React 19 では自動最適化も進んでいるけど、 useCallback の考え方を知っておくこと自体がすごく大事

次の 第85章 では、

React.memouseCallback を組み合わせて、 実際に子コンポーネントのムダな再レンダーを止めてみる」

という 実践編 を一緒にやっていきます 💻✨ コンソールにログを出しながら、「ほんとに再レンダー減ってる!」を体感してみよう〜 😆