みなさん、こんにちは!Reactの世界へようこそ。
Reactを学び始めて、画面が動くようになると本当にワクワクしますよね。でも、開発を進めていくうちに、こんな不思議な現象に出会ったことはありませんか?
「画面が何度もチカチカして、処理が止まらなくなっちゃった!」
「`useEffect`の依存配列(右下の `[]` の中身)に黄色い波線が出て、『関数を入れなさい』って怒られる……。でも、入れると無限ループになりそうで怖い!」
大丈夫、安心してください。これはReactを学ぶ誰もが、本当に100人中100人が一度は通る「お約束の壁」です。決してあなたの理解が足りないわけではありません。
今回は、この問題の救世主となる`useCallback`と`useEffect`の連携パターンについて、専門用語をできるだけ使わず、身近なお買い物の例え話を使って、世界一優しく解きほぐしていきます。
この記事を読み終える頃には、「あぁ、そういうことか!」とスッキリ納得して、自信を持ってコードが書けるようになりますよ。一緒に一歩ずつ進んでいきましょう!
—
1. なぜ「関数」をuseEffectに入れると暴走するの?
まずは、身近な例え話から始めてみましょう。
あなたのお家に、代わりに買い物に行ってくれる「お買い物ロボット(useEffect)」がいると想像してください。
あなたはロボットに、「このお買い物メモ(関数)に書いてあるものを買ってきてね」とお願いしました。
ロボットはとても真面目なので、「メモの内容が変わったときだけ、スーパーに買い出しに行く」というルールで動いています。これが、`useEffect`の「依存配列」の仕組みです。
ここで、Reactのちょっとした「お茶目なクセ」が登場します。
Reactは、画面のパーツ(コンポーネント)が新しく書き換わる(再レンダリングされる)たびに、「お買い物メモ」を全く同じ内容で新しく紙に書き直して、あなたに手渡すのです。
- 1回目の買い物: 「牛乳」と書かれたメモを渡す。ロボットはスーパーへ行く。
- 画面が更新される: あなたは、全く同じ「牛乳」という文字を書いた新しいメモ用紙をロボットに渡す。
- ロボットの反応: 「あ、新しいメモ用紙だ!前の紙とは別物だから、内容が同じでも、もう一度スーパーに行かなきゃ!」
ロボット(useEffect)からすると、「紙(関数の実体)が新しくなった=新しい命令が出た」と勘違いしてしまうのです。その結果、ロボットは休む間もなくスーパーと家を往復し続けてしまいます。これが「無限ループ(暴走)」の正体です。
—
2. 救世主 `useCallback` の登場!
このロボットの勘違いを防ぐために登場するのが、`useCallback`(ユーエス・コールバック)です。
`useCallback`を一言でいうと、「お買い物メモをラミネート加工して、ずっと使い回す技術」です。
「このメモ(関数)は、中に書いた条件(依存配列)が変わらない限り、新しく書き直さずに同じラミネートカードをずっと使い回してね!」とReactに指示を出します。
そうすれば、画面が何度書き換わっても、ロボットに渡されるのは「まったく同じラミネートカード」。ロボットも「あ、さっきと同じカードだから、買い物に行かなくていいや」と賢く判断してくれるようになります。
—
3. 実際のコードで見てみよう!
では、実際にエディタに貼り付けて動かせるコードを見てみましょう。
今回は「ボタンを押すと、お買い物リスト(データの取得)をやり直す」というシンプルなアプリを作ってみました。
コードの中のコメントで、何が起きているかを優しく解説しています。
import React, { useState, useEffect, useCallback } from ‘react’;
function ShoppingApp() {
const [category, setCategory] = useState(‘野菜’);
const [count, setCount] = useState(0); // 買い物の回数カウント
// ==========================================
// ★ ここがポイント! useCallbackで関数を包みます
// ==========================================
// これにより、「category」が変わらない限り、
// fetchItems という関数(メモ用紙)は新しく作り直されず、再利用されます。
const fetchItems = useCallback(() => {
console.log(`📡 【API通信】「${category}」のデータをインターネットから取得しました!`);
}, [category]); // ← 「category」が変わった時だけ、関数を新しく作り直します
// ==========================================
// ★ useEffect の中で、上で作った関数を呼び出します
// ==========================================
useEffect(() => {
// 1. 関数を実行してお買い物データを取ってくる
fetchItems();
console.log(‘🤖 [useEffect] 関数が実行されたので、お買い物データを更新しました。’);
}, [fetchItems]); // ← 安全に「fetchItems」を依存配列に入れることができます!
return (
🛒 お買い物マネージャー
現在のカテゴリ: {category}
{/ カテゴリを変えると、fetchItemsが作り直され、useEffectが動きます /}
{/
このボタンを押すと「count」が増えて、画面が再レンダリング(再描画)されます。
しかし、fetchItemsは「useCallback」で守られているため、
画面が再描画されても関数は作り直されず、余計なAPI通信(暴走)は起きません!
/}
ただのカウンター: {count}
);
}
export default ShoppingApp;
このコードの「ここがすごい!」
1. 「カウントを増やす」ボタンを押したとき:
画面は新しく描き直されますが、`category` は変わっていません。そのため、`useCallback` は以前と同じ「ラミネートされた関数」をそのまま返します。`useEffect` は「何も変わっていないな」と判断し、余計な通信を一切行いません。
2. 「カテゴリを『果物』にする」ボタンを押したとき:
`category` が変わったので、`useCallback` は新しいメモ(果物用の関数)を作ります。`useEffect` も「メモが変わった!」と気づいて、正しく新しいデータを取得しに行きます。
見事に、無駄な動きを完璧にコントロールできていますね!
—
4. つまずきやすいポイントへの「そっとフォロー」
ここで、もしかしたらこんな疑問が浮かんだかもしれません。
「そもそも、なんで関数を依存配列(`[]`)に入れなきゃいけないの?」
「入れなければ、無限ループも起きないし楽じゃない?」と思いますよね。
実は、Reactのルール(ESLintというコードの監視役)が「useEffectの中で使っている外の変数は、全部依存配列に入れなさい」と強く勧めてきます。
なぜなら、依存配列に入れないと、「古いデータを持ったままの幽霊のような関数」をずっと使い続けてしまい、画面の表示がバグってしまう原因になるからです。
Reactは常に、「今の最新の状態」を正しく画面に映し出したい、とても真っ直ぐで正直な仕組みを持っています。だからこそ、
1. `useEffect` の中で使う関数は、ちゃんと依存配列に入れる。
2. そのままだと暴走しちゃうから、`useCallback` でラミネートしてあげる。
この2つのセットが、Reactの世界で美しく安全にコードを書くための王道のテンプレートになっているのです。
—
まとめ:今日のポイントをおさらい!
- Reactは、画面が更新されるたびに関数を「新しく作り直す」クセがある。
- `useEffect`の依存配列に関数を入れると、毎回「新しくなった!」と勘違いして暴走(無限ループ)しやすい。
- `useCallback` は、関数をラミネート保存して「中身が変わらない限り使い回す」ための道具。
- この2つを組み合わせることで、余計な通信やバグのない、軽快でプロフェッショナルなアプリが作れる!
最初は「なんだか難しそうだな…」と感じるかもしれませんが、何度もコードを書いて動かしていくうちに、「あ、ここでラミネート(useCallback)の出番だ!」と直感的にわかる瞬間が必ずやってきます。
焦らず、自分のペースで、この可愛いロボット(React)との付き合い方を楽しんでいってくださいね。あなたのReact開発を心から応援しています!

コメント