Next.jsで学ぶReact講座(完全初心者向け・30日) | 第4章:実用的React – useEffect入門

React Next.js
スポンサーリンク

useEffectとは

「画面の変化に合わせて“副作用”を実行する仕組み」

ここまで扱ってきた useState は、「値(状態)を覚えておく仕組み」でした。
useEffect は、それとは別の役割を持っています。

一言でいうと、
「コンポーネントの描画(レンダリング)のあとに、追加でやりたい処理を書く場所」
です。

例えば、こんなときに使います。

  • 画面が表示されたタイミングでログを出したい
  • API からデータを取得したい
  • ブラウザのタイトル(document.title)を更新したい
  • ローカルストレージから値を読み書きしたい

これらは、ただ JSX を返すだけではできない「副作用(サイドエフェクト)」です。
React の世界では、UI を作る関数(コンポーネント)と“副作用”を行う処理を分けるために useEffect を使います。


useEffectの基本形

書き方と「いつ実行されるか」の考え方

基本形はこの形です。

useEffect(() => {
  // ここに副作用の処理を書く
}, [依存値]);
TSX

それぞれの意味を整理するとこうなります。

第一引数:() => { ... } の部分
実際に実行したい処理(関数)を書く場所

第二引数:[依存値] の部分(依存配列)
「どのタイミングでこの処理をもう一度実行するか」を React に伝える

この「依存配列」が useEffect のキモです。
ここに何を書くかで、

  • 初回だけ実行
  • 特定の state が変わるたびに実行
  • 毎回のレンダリングごとに実行(依存配列を省略した場合)

が変わります。


初回処理(マウント時の一度だけ実行)

「ページが表示されたタイミングで一度だけ」の典型パターン

例えば、「コンポーネントが表示されたときにだけログを出したい」場合。

import { useEffect } from "react";

function Example() {
  useEffect(() => {
    console.log("コンポーネントがマウントされました");
  }, []);

  return <p>Example コンポーネントです。</p>;
}
TSX

ここで重要なのは、useEffect の第二引数が [](空配列)になっていることです。

意味としては、

  • 初回レンダリング後に
  • コールバック(() => { ... } の中身)を一度だけ実行し
  • その後は再レンダリングがあっても実行しない

という指定になります。

Next.js では、このパターンは例えばこんな用途に使えます。

  • 初回表示時にアクセスログを送る
  • 最初にだけローカルストレージから設定を読み込む
  • 初回にだけ外部 API を叩いてデータを取得する

「画面が出たタイミングで、1 回だけ何かしたい」ときは、とりあえず useEffect(() => { ... }, []) を思い出せばOKです。


状態変更との関係

特定の state が変わったときにだけ実行する

useEffect は、「依存している値が変わったとき」に再実行されます。
例えば、カウントが変わるたびに処理をしたい場合を考えましょう。

import { useEffect, useState } from "react";

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

  useEffect(() => {
    console.log("count が変わりました:", count);
  }, [count]);

  return (
    <main>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>増やす</button>
    </main>
  );
}
TSX

この場合の流れを丁寧に追ってみます。

最初の表示
count の初期値は 0。
初回レンダリング後に useEffect が実行され、「count が変わりました: 0」とログが出る。

ボタンクリック
setCount(count + 1) によって count が 1 になる。
state が変わったのでコンポーネントが再レンダリングされる。
[count] と指定されているため、「count が前回と違う」と判断され、useEffect が再度実行される。
コンソールには「count が変わりました: 1」とログが出る。

さらにボタンを押すたびに、同じようにログが更新されます。

ポイントは、

  • useEffect の依存配列に書いた値(ここでは count)が変わると、もう一度実行される
  • 変わっていなければ実行されない

ということです。

「状態 → それに応じた副作用」という関係

この考え方はとても重要です。

コンポーネントの中では、

  • JSX の中で「状態 → 画面の見た目」を書き
  • useEffect の中で「状態 → 外部への影響(ログ、API、DOM 操作など)」を書く

という役割分担になります。

例えば、次のようなパターンが典型です。

  • searchQuery が変わったら、API に検索リクエストを飛ばす
  • isDarkMode が変わったら、document.body にクラスを付け替える
  • userId が変わったら、そのユーザー詳細を取得する

いずれも、「ある状態が変化したら、それに応じて副作用を実行する」という構造になっています。


よくある無限ループ

「useEffect の中で state を更新 → また useEffect が走る」のパターン

初心者が useEffect で一番ハマるのが「無限ループ」です。
典型的なダメな例を先に見ておきましょう。

import { useEffect, useState } from "react";

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

  useEffect(() => {
    setCount(count + 1);
  }, [count]);

  return <p>カウント: {count}</p>;
}
TSX

一見、「count が変わるたびに +1 する」という意味に見えますが、実際にはこうなります。

初回レンダリング
count は 0
useEffect 実行 → setCount(1)

state 更新
count が 1 になる → 再レンダリング
「count が 0 → 1 に変わった」ので、依存配列 [count] により useEffect が再度実行
useEffect 実行 → setCount(2)

state 更新
count が 2 になる → 再レンダリング
また useEffect 実行 → setCount(3)

…これが永遠に続く

つまり、

  • 「count が変わったら useEffect 実行」
  • 「useEffect の中で count を変えている」

という自己再帰みたいな状態になってしまっています。

どうすれば防げるか

大事なのは、「依存配列に書いた値を、useEffect の中でどう扱うか」を意識することです。

無限ループになりやすいパターンの特徴は、

  • 依存配列に count が入っている
  • かつ useEffect の中で setCount を呼んでいる

という組み合わせです。

これを回避する典型的な方法は、

「本当にその state を依存にする必要があるか?」

を見直すことです。

例えば、「初回にだけ一度だけ実行したい処理」なのに [count] を入れていたりする場合は、
素直に [](空配列)にするべきです。

useEffect(() => {
  // 初回だけやりたい処理
}, []); // count を依存にしない
TSX

あるいは、

「何かの計算をして state に反映したい」けど、それが再レンダリングのたびに走る意味はない
「レスポンスを state に入れるだけ」で、依存のほうは userId だけでよい

など、ロジックを少し整理することで無限ループは避けられます。

もう一つのよくある誤解

依存配列を空にしないと「常に最新の値を取れない」と思って、
なんでもかんでも state を全部依存配列に入れてしまうパターンも危険です。

useEffect の中から参照する値を「全部」依存に入れようとすると、
結果的に「どれかが変わるたびに useEffect が走る」ことになり、
「その中で state を更新して → それでまた依存が変わって → ループ」という罠に入りやすくなります。

初心者のうちは、まずは次の 3 パターンの使い分けに慣れるのがおすすめです。

  • 「初回だけ」 → useEffect(() => { ... }, [])
  • 「この state が変わるたびに」 → useEffect(() => { ... }, [特定の state だけ])
  • 「副作用の中で安易に setState を呼ばない(本当に必要なときだけ)」

このルールを守るだけで、無限ループの 8 割くらいは防げます。


まとめと、useEffectを怖がらないために

useEffect は、コンポーネントのレンダリング後に実行される「副作用用のフック」で、ログ出力・API 呼び出し・DOM 直接操作など「UI 以外の仕事」を担当する。
第二引数の依存配列で「いつ実行するか」が決まり、[] なら「初回だけ」、[count] なら「count が変わるたび」というように、状態と処理の関係を宣言できる。
無限ループは「依存に入れた state を、useEffect の中でまた更新してしまう」ことで起きるので、依存配列と副作用内の setState の関係に気をつけることが重要になる。

次のステップとしては、

  • 「初回にだけ API からダミーデータを取ってきて表示するコンポーネント」
  • 「検索キーワードが変わるたびにコンソールにログを出すコンポーネント」

などを自分で書いてみると、useEffect の「タイミング」と「依存配列」の感覚がかなり掴めてきます。

タイトルとURLをコピーしました