KAKUTA TECH BLOG

Blog Article

useEffect を理解する

はじめに

この記事では、React の useEffect を「書き方」ではなく「なぜ存在するのか」という仕組みから理解していきます。初学者の方がつまずきやすいポイントである、レンダーコード・イベントハンドラ・副作用の違いを整理しながら、useEffect が必要になる理由を順番に説明します。

参考にしたリンク

https://react.dev/learn/synchronizing-with-effects

https://react.dev/learn/you-might-not-need-an-effect

useEffect とはなにか?

一言で言うと、useEffect は「レンダーによって必要になった副作用を書く場所」です。

なぜこのような場所が必要なのかを理解するには、まず React のコンポーネントの中にあるコードが、役割によって 2 種類に分かれていることを知る必要があります。

React のコンポーネントには、「レンダーコード」と「イベントハンドラ」という 2 つの場所があります。

レンダーコードは、propsstate を受け取り、それをもとに JSX を計算して返す場所です。ここは純粋な計算の場所であり、同じ入力であれば必ず同じ JSX を返す必要があります。

一方、イベントハンドラは、ボタンのクリックなどユーザーの操作をきっかけに何かの処理を実行する場所です。ここでは state の更新やログ出力など、プログラムの状態を変える処理、つまり副作用が許されています。

しかし、この 2 つだけでは実現できない処理があります。

例えば、コンポーネントが画面に表示されたときにサーバーへ接続したい場合や、DOM を直接操作したい場合です。これらはユーザーの操作とは関係なく、「レンダーされたこと」自体をきっかけに実行したい処理です。

このような処理を書くための場所が useEffect です。

ちなみに、state の更新、サーバー通信、DOM 操作のような関数の外に影響を与える処理のことを副作用といいます。これはイベントハンドラに限らず、もっと広い意味を持ちます。

なぜレンダー中に副作用を書いてはいけないのか?

レンダーは JSX を計算する純粋な処理でなければなりません。なぜなら、React は再レンダーを何度も行う設計になっているからです。

もしレンダー中にサーバー接続や DOM 操作を書いてしまうと、レンダーが走るたびにそれらの処理が実行されてしまいます。これはバグの原因になります。

例えば、レンダー中にサーバー接続を書くと、再レンダーのたびに接続が増えていきます。ユーザーが気づかないうちに、接続が何重にも積み重なってしまいます。

そのため、React は「レンダーでは副作用を書かない」というルールを設けています。そして、副作用を書く専用の場所として useEffect が用意されています。

useEffect はいつ実行されるのか?

useEffect は、コンポーネントがレンダーされて画面が更新された後に実行されます。

これは非常に重要です。レンダー中ではなく、レンダー後です。

React の処理の流れは次のようになっています。

レンダー(JSX の計算)
↓
DOM の更新
↓
useEffect の実行

そのため、レンダーの純粋性を保ったまま、副作用を安全に実行することができます。

useEffect の基本的な書き方

useEffect は次の 3 ステップで書きます。

  1. エフェクトを宣言する
  2. 依存配列を指定する
  3. 必要ならクリーンアップを書く
コード例
// App.jsx
import { useEffect } from "react";

export default function App() {
  useEffect(() => {
    console.log("サーバーに接続しました");
  }, []);

  return <h1>Hello</h1>;
}

このコードでは、コンポーネントが画面に表示された後に、console.log が実行されます。

レンダー中ではなく、レンダー後に実行されていることがポイントです。

依存配列の意味

useEffect の第 2 引数には依存配列を指定します。これによって、React に「どの値が変わったときにエフェクトを再実行するか」を伝えることができます。

  • 依存配列がない場合、毎回レンダー後に実行されます。
  • 空配列 [] の場合、マウント時のみ実行されます。
  • [a, b] の場合、a または b が変化したときに実行されます。

依存配列は、不要な再実行を防ぐために存在します。

依存配列はパフォーマンスのためだけではありません。値が変わったときにだけ処理を実行したい場合に使用します。例えば、特定の id が変わったときだけ再接続したい場合です。

マウントとアンマウント

マウントとは、コンポーネントが画面に表示されることです。アンマウントとは、画面から消えることです。

もしクリーンアップを書かずにサーバー接続をしていると、ページ遷移のたびに接続が積み重なっていきます。このバグを見つけるために、開発環境では React は初回マウント直後に再マウントを行います。

クリーンアップが正しく書かれていないと、ここで問題が表面化します。

クリーンアップの重要性

サーバー接続やタイマーなどは、停止処理が必要です。これをクリーンアップ関数として返します。

useEffect(() => {
  const id = setInterval(() => {
    console.log("実行中");
  }, 1000);

  return () => {
    clearInterval(id);
  };
}, []);

このクリーンアップがないと、再マウント時に処理が積み重なってしまいます。

React は開発環境で、初回マウント直後にわざと再マウントを行います。これは、クリーンアップ漏れのバグを見つけやすくするためです。

まとめ

  • レンダーは純粋な計算である
  • 副作用はレンダーに書いてはいけない
  • イベントハンドラでも書けない副作用がある
  • それを書く場所が useEffect
  • useEffect はレンダー後に実行される
  • 依存配列で再実行の条件を制御する
  • クリーンアップは必須である

useEffect は書き方を覚えるものではなく、レンダーの仕組みを理解した結果として自然に必要になるフックです。