0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

useEffectをとりあえず理解する

Posted at

useEffectを理解したい

Reactの副作用を扱うフックuseEffect。useEffectはどんなもので、なぜ必要で、どのように使うのかを解説していきます。

そもそもuseEffectで何をするの?

React の useEffect は、副作用(Side Effect)を扱うための必須フックです。サーバー(APIやデータベース)、ブラウザAPI(位置情報、カメラなど)、時間システム(タイマー)など、外部サービスとの連携を管理し、レンダー後の適切なタイミングで実行します。

特にAPI通信で最もよく使われます。

Effectとは「作用」「効果」「影響」という意味があります。useEffectが扱うのはSide Effectで「副作用」になります。

API通信
// サーバーからデータを取得
function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("/api/users")
      .then((response) => response.json())
      .then(setUsers);
  }, []); // 初回のみ実行

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
非同期通信
// リアルタイム時計
function Clock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);

    return () => clearInterval(timer); // クリーンアップ
  }, []);

  return <div>{time.toLocaleTimeString()}</div>;
その他
function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // ページタイトルを動的に変更
    document.title = `カウント: ${count}`;
  }, [count]);

  useEffect(() => {
    // ローカルストレージに保存
    localStorage.setItem("count", count.toString());
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

なぜuseEffectを使うのか

useEffectを使うメリットはなんでしょうか。

外部のAPIからデータを取得する際を例に解説します。

通常のJavaScript
function BadExample() {
  const [users, setUsers] = useState([]);

  // ❌ これをレンダリング中に書くと大変なことに!
  fetch("/api/users")
    .then((response) => response.json())
    .then((data) => setUsers(data));

  return (
    <div>
      <h1>ユーザー一覧</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

この例の場合、

  1. コンポーネントがレンダリングされる
  2. fetch('/api/users')が実行される
  3. API からデータが返ってくる
  4. setUsers(data)で状態が更新される
  5. 状態が更新されたので再レンダリング ← ここがポイント!
  6. また fetch('/api/users')が実行される
  7. 無限ループ!
    となってしまいます。

それにより、APIを無限に叩き続けたり、ブラウザがフリーズ。サーバーに大きな負荷をかけてしまいます。

useEffect
function GoodExample() {
  const [users, setUsers] = useState([]);

  console.log("レンダリング中...");

  // ✅ useEffectで副作用を適切なタイミングで実行
  useEffect(() => {
    console.log("useEffect実行:APIを呼び出します");
    fetch("/api/users")
      .then((response) => response.json())
      .then((data) => setUsers(data));
  }, []); // 空の依存配列 = 初回のみ実行

  return (
    <div>
      <h1>ユーザー一覧</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

useEffectの場合、

  • コンポーネントがレンダリングされる
  • useEffect が初回のみ実行される ← ここがポイント!
  • API からデータが返ってくる
  • setUsers(data)で状態が更新される
  • 再レンダリングされる
  • useEffect は実行されない(依存配列が空なので)
  • 正常に動作!

となり、必要なときだけAPIを呼び出すことができ、パフォーマンスが向上します。
また、予測可能で安全な動作をさせることができます。

副作用とは

副作用と聞くと、「薬の副作用」をイメージしがちですが本来は悪いだけでなく、メインとする作用(主作用)とは別の作用を指すものです。

主作用と副作用

主作用(コンポーネント内の処理)
  • 変数の計算
  • JSX の生成
  • 状態の管理
  • 条件分岐

などはコンポーネント内の処理で副作用ではありません。
主作用です。

副作用(コンポーネントの外の処理)
  • サーバー・データベース(API 呼び出し、データ保存)
  • ブラウザ API(位置情報、カメラ、ストレージ)
  • 時間システム(タイマー、遅延処理)
  • 外部ライブラリ・SDK(分析ツール、地図 API)
  • WebSocket・リアルタイム通信
  • デバイス API(カメラ、マイク、センサー)

などはコンポーネントの外の処理なので副作用です。

useEffectはその副作用を扱うフックです。

純関数と副作用

useEffectをより理解する上で、純関数と副作用という概念について説明します。
この2つは対極の概念になります。

Reactはコンポーネントを純関数として書くこと求めます。

純関数-常に同じものを返す

純関数とは以下のような特徴を持つ関数のことを指します。

・自分の仕事に集中する。呼び出される前に存在していたオブジェクトや変数を変更しない。
・同じ入力には同じ出力。同じ入力を与えると、純関数は常に同じ結果を返す。

純関数
function double(number) {
  return 2 * number;
}

上記の例ではdouble関数を呼び出したとき、必ず2の倍数を返します。

Reactは、コンポーネントは常に同じものを返す純関数であるという概念に基づいて設計されています。

副作用-意図しない処理

// ❌ 外部変数を変更する危険なコンポーネント
let studentNumber = 200;

function StudentCard() {
  // ❌ 外部変数を読み書きしている
  studentNumber = studentNumber + 1;
  return (
    <div className="student-card">
      <p>生徒番号: {studentNumber}</p>
      <p>氏名: 学生 太郎</p>
    </div>
  );
}

export default function ClassRoom() {
  return (
    <div className="classroom">
      <StudentCard />
      <StudentCard />
      <StudentCard />
    </div>
  );
}

// 結果:
// 生徒番号: 201 氏名: 学生 太郎
// 生徒番号: 202 氏名: 学生 太郎
// 生徒番号: 203 氏名: 学生 太郎

// 問題:同じコンポーネントなのに毎回違う結果

このコンポーネントは外部で宣言されたstudentNumberを読み書きしています。
するとそのコンポーネント関数が返す値は、呼び出すごとに違う結果を表示します。

他のコンポーネントもstudentNumberを読み込む場合、予測が不可能になります。
これが、副作用がもたらすバグです。

Reactではコンポーネント関数にpropsを渡すことで純関数にすることができます。

function StudentCard({ studentNumber, studentName }) {
  return (
    <div className="student-card">
      <p>生徒番号: {studentNumber}</p>
      <p>氏名: {studentName}</p>
    </div>
  );
}

export default function ClassRoom() {
  return (
    <div className="classroom">
      <StudentCard studentNumber={201} studentName={"学生 太郎"} />
      <StudentCard studentNumber={201} studentName={"学生 太郎"} />
      <StudentCard studentNumber={201} studentName={"学生 太郎"} />
  );
}

// 結果:
// 生徒番号: 201 氏名: 学生 太郎
// 生徒番号: 201 氏名: 学生 太郎
// 生徒番号: 201 氏名: 学生 太郎

StudentCardコンポーネントはpropsのみに依存していて純関数となります。

Reactでは各コンポーネントは「自分のことだけを考えるべき」とされています。
そうしなければ、コンポーネントのレンダリング中にデータが競合状態に陥ったり、予測不可能な実行順序になることがあります。

しかし、アプリケーションはデータベースなどとのやりとりは避けられません。

useEffectの役割

useEffectはその外部サービスとの連携を安全に管理するフックです。
副作用を利用するツールでも、副作用を実行するツールでもありません。

useEffect は「外部サービス連携マネージャー」として考えると理解しやすくなります。コンポーネントが外部のサーバー、ブラウザ API、タイマーなどと連携する必要がある時、useEffect が適切なタイミングでその連携を実行し、不要になったリソースを安全に解放してくれます。

これにより、開発者は外部サービスとの複雑な連携を、シンプルで安全な方法で実装できるようになります。レンダー中に実行すると問題が起きる処理を、useEffect が適切なタイミング(レンダー後)で実行してくれます。

useEffectの基本的な使い方

useEffectの基本構文

useEffect(setup, dependencies?)

useEffectは引数にセットアップ関数と、オプションでクリーンアップ関数と依存配列を持ちます。

セットアップ関数は、オプションでクリーンアップ関数を返すことができます。コンポーネントが初めて DOM に追加されると、React はセットアップ関数を実行します。依存配列 (dependencies) が変更された再レンダー時には、React はまず古い値を使ってクリーンアップ関数(あれば)を実行し、次に新しい値を使ってセットアップ関数を実行します。コンポーネントが DOM から削除された後、React はクリーンアップ関数を最後にもう一度実行します。

セットアップ関数

useEffectのメインとなる部分で、外部サービスとの連携処理を書きます。useEffectが実行されるたびに呼ばれます。
レンダリング後に実行されます。

useEffect(() => {
  // ここがセットアップ関数
  fetch("/api/data"); // サーバーとの連携
  localStorage.getItem("key"); // ストレージとの連携
  setInterval(() => {}, 1000); // 時間システムとの連携
});
クリーンアップ関数

依存配列の値が変わってuseEffectが再実行される際に、古い値を使ってクリーンアップ関数を先に実行し、古いリソースを適切に開放します。その後で新しい値を使ってセットアップ関数を実行します。
これによりタイマーやイベントリスナーなどのリソースは、クリーンアップ関数で適切に解放することでメモリリークや競合状態を防ぎます。

function TimerComponent({ interval }) {
  useEffect(() => {
    console.log("🚀 セットアップ関数実行:", interval);

    const timer = setInterval(() => {
      console.log("タイマー実行");
    }, interval);

    return () => {
      console.log("🧹 クリーンアップ関数実行:", interval);
      clearInterval(timer);
    };
  }, [interval]);

  return <div>タイマーコンポーネント</div>;
}
3つの実行パターン

useEffectには3つの実行するパターンがあります。

// パターン1: 毎回実行
useEffect(() => {
  console.log("レンダリングのたびに実行");
});

// パターン2: 初回のみ実行
useEffect(() => {
  console.log("初回のみ実行");
}, []);

// パターン3: 特定の値が変わった時だけ実行
useEffect(() => {
  console.log("値が変わった時だけ実行");
}, [依存する値]);
依存配列なしは毎回実行

あまり使うことはないかもしれません。
レンダリングのたびに毎回実行されます。

依存配列ありは指定された値が変わったとき以外はスキップ

useEffectの基本の形はこれになります。
依存配列がありの場合は、指定された値が変わったときに実行されます。
それ以外はスキップとなります。これにより、不要な処理を避けてパフォーマンスを向上させることができます。

空の配列の場合、初回レンダリング時に実行されます。
値を指定する場合は、その値が変更されたときに実行されます。

useEffectは最終手段?

useEffectは副作用を分離する有効な手段です。
ただし、公式ドキュメントにはこうも書かれています。

いろいろ探してもあなたの副作用を書くのに適切なイベントハンドラがどうしても見つからない場合は、コンポーネントから返された JSX に useEffect 呼び出しを付加することで副作用を付随させることも可能です。これにより React に、その関数をレンダーの後(その時点なら副作用が許されます)で呼ぶように指示できます。ただしこれは最終手段であるべきです。

それは実行タイミングが予測しにくかったり、パフォーマンスへの影響、たくさん使いすぎるとデバッグが困難になるなどがあるようです。

API通信などはReact19以降はuseAPIやuseSWR、TanStackQueryなどを使う機会が多いのかもしれません。

まとめ

useEffectについてまとめてみました。
 
useEffectは副作用を適切に管理するための基本のフックです。
Reactコンポーネントは純関数であるべきで、副作用をuseEffectで分離することができます。
空の配列で初回のみ実行や特定の値が変わったときなど副作用の実行を制御して不要な実行を防ぐことができます。

しかし、公式ドキュメントにもあるようにuseEffectの利用は安易にせず、必要かどうかを考える必要はあるようです。そのためにはuseEffectへのまずは理解が必要です。

最後までお読みいただきありがとうございます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?