ようこそ、Reactの世界へ!フロントエンドの迷宮を共に歩む仲間として、あなたを心から歓迎します。
Reactを学び始めると、誰もが一度は「ブラウザが固まった!」「ファンが爆音で回り出した!」という恐怖の体験をします。その正体の多くは、今回お話しする「useEffectによる無限ループ」です。
これは決してあなたがプログラミングに向いていないわけではなく、Reactという非常に真面目な相棒との「言葉のすれ違い」が生んでいるだけなんです。今日は、そのすれ違いをどう解きほぐしていくか、現場の泥臭い知見を交えながら、優しく解説していきますね。
—
1. `useEffect` は「お片付け」と「次の約束」
まず、`useEffect`を難しく考えすぎないでください。これは、「画面を描き戻した(レンダリングした)後に、ついでにやっておいてね」という、Reactへのお願い事です。
例えば、お買い物のリストを表示した後に「ついでに在庫をサーバーに確認してね」とか、「画面が表示されたら、挨拶のメッセージを出してね」といった具合です。
無限ループはなぜ起きる?
無限ループが起きる仕組みは、実はとってもシンプルで、かつ悲しい「勘違い」から生まれます。
1. React: 「よし、画面を描き終わったぞ!」
2. useEffect: 「あ、描き終わった? じゃあ、このデータ(State)を更新しておいて!」
3. React: 「えっ、データが変わったの? じゃあ、もう一回画面を描き直さなきゃ!」
4. React: 「よし、描き直したぞ!(2回目)」
5. useEffect: 「あ、描き終わった? じゃあ、またこのデータを更新して!」
6. React: 「えっ、また!? じゃあ描き直しだ!」
…これが永遠に続くのが無限ループです。まるで、自分の尻尾を追いかけてぐるぐる回るワンちゃんのような状態ですね。
—
2. 現場でよく見る「無限ループ」の再現コード
まずは、あえて「失敗するコード」を見てみましょう。どこがループの原因か、一緒に探してみてください。
import React, { useState, useEffect } from ‘react’;
function InfiniteCounter() {
const [count, setCount] = useState(0);
// 【危険!】依存配列がないため、レンダリングのたびに実行されます
useEffect(() => {
console.log(‘useEffectが動いたよ!’);
// ここでstateを更新すると…
// 1. レンダリング完了
// 2. useEffect実行
// 3. setCountでstate更新
// 4. 再レンダリング発生(1に戻る)
setCount(count + 1);
});
return (
現在のカウント: {count}
※これをお手元の環境で動かすと、ブラウザが重くなるので注意してくださいね
);
}
—
3. 無限ループを止める「魔法の鍵」:依存配列
この暴走を止めるために必要なのが、`useEffect`の第2引数に渡す `[]`(依存配列) です。これはReactへの「条件付け」になります。
パターンA:最初の一回だけやってほしい時
「お店に入った時の最初の一回だけ挨拶してね」という場合は、空の配列 `[]` を渡します。
useEffect(() => {
// ここは最初の一回(マウント時)しか動きません
console.log(‘最初だけ実行されるよ’);
}, []); // ← この空っぽの箱が「最初だけ」の合図!
パターンB:特定のデータが変わった時だけやってほしい時
「お買い物の合計金額が変わった時だけ、ポイントを計算し直してね」という場合は、配列の中にその変数を入れます。
useEffect(() => {
console.log(‘countが変わった時だけ動くよ’);
}, [count]); // ← countを監視してね、という合図
—
4. もっと賢い回避策:関数型の更新
「今のカウントに +1 したいだけなのに、依存配列に `count` を入れると、結局またループしそうで怖い…」
そんな時に使えるプロの技が、「関数型の更新」です。これを使えば、`useEffect`の中で `count` という変数自体を参照しなくて済むので、依存配列を空に保つことができます。
import React, { useState, useEffect } from ‘react’;
function SafeCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
// 1秒ごとにカウントアップするタイマーを作ってみましょう
const timer = setInterval(() => {
// setCountの中に「今の値をどうしたいか」という関数を書きます
// こうすることで、useEffectの外にある ‘count’ 変数を見に行かなくて済みます
setCount(prevCount => prevCount + 1);
}, 1000);
// 【重要】クリーンアップ関数
// コンポーネントが消える時にタイマーを止める「お片付け」です
return () => clearInterval(timer);
}, []); // 依存配列は空っぽでOK!
return (
安全なカウンター
数値: {count}
このカウンターは無限ループせず、1秒ごとに静かに増えていきます。
);
}
—
5. 【中級編】意外な落とし穴「オブジェクトと配列」
最後に、少しだけ背伸びしたお話を。実は、依存配列に「オブジェクト」や「配列」を入れると、中身が変わっていなくても無限ループが起きることがあります。
// 毎回新しいオブジェクトが作られる
const userInfo = { name: ‘たろう’ };
useEffect(() => {
// Reactは「前回のuserInfo」と「今回のuserInfo」を比較しますが、
// JavaScriptのルールで、見た目が同じでも { } は毎回「別物」と判定されます。
console.log(‘設定が変わったよ’);
}, [userInfo]); // ← これで無限ループが発生することがあります
これを防ぐには、`useMemo`を使ってオブジェクトを固定したり、オブジェクトの中の特定の文字列(`userInfo.name` など)だけを監視するようにしましょう。
—
最後に:つまずいたあなたへ
無限ループを起こして画面が真っ白になると、最初は「やってしまった!」と焦るものです。でも、それはあなたが 「Reactを動かそうと一生懸命コードを書いた証」 です。
1. 「何かが変わったら実行」の「何か」を正しく指定できているか?
2. その「実行」の中で、また「何か」を変えてしまっていないか?
この2点を優しく自分に問いかけてみてください。
Reactは魔法ではありません。あなたが書いた指示を、愚直なまでに守ろうとしているだけなんです。少しずつ、この実直な相棒とのコミュニケーションを楽しんでいってくださいね。
もしまたループしてしまったら? 大丈夫、ブラウザをリロードして、ゆっくり依存配列を眺め直せばいいだけですから。
応援していますよ!

コメント