【React】エフェクトとは何か?useEffectフックの使い方や副作用(Side Effect),クリーンアップを実例で解説

react-prograshi(プロぐらし)-kv React.js
記事内に広告が含まれていることがあります。

React開発で避けて通れないのが、useEffectフックです。

これは、コンポーネントの状態(state)と外部システム(DOM、APIなど)を同期させるために使われます。

本記事では、このエフェクトの仕組みや、関連性が高い用語「副作用(Side Effect)」について解説しています。


エフェクトとは何か?

Reactの「エフェクト」とは、Reactの外側にあるシステムとコンポーネントの状態を同期させるために使う機能です。

例えば、APIによるデータ取得、DOMイベントの登録(ブラウザの操作など)、購読の開始/解除、外部ライブラリとのやりとりなどをする際に使用します。

補足

Reactのコンポーネントの主な役割は、props(親から渡されるデータ)とstate(コンポーネント自身の状態)に基づいてUI(画面)を表示(レンダリング)することです。

エフェクトはpropsやstateでは対応できない処理を実現することができます。


副作用(Side Effect)とは何か?

エフェクトと関連性が高い言葉に「副作用(Side Effect)」があります。

副作用とはReactに限らずプログラミングで使う用語で、ある関数や処理が実行されたときに、その関数のスコープの外にある状態(外部システム)を変化させる、または外部システムとやり取りするすべての動作のことを指します。

つまり、エフェクトは、Reactコンポーネント内で副作用を安全かつ予測可能な方法で実行・管理する仕組みといえます。


エフェクトの使い方(useEffect)

useEffectの基本形

Reactでは、エフェクトを定義するためにuseEffectというReactフックを使用します。

useEffectではレンダー後に実行する処理を記述します

useEffect(副作用を持つ関数, [依存配列])
useEffectの引数

useEffectは次の2つの引数をとります。

  1. 副作用を持つ関数(Effect Function): 実行したい処理(副作用)を記述します。
  2. 依存配列(Dependency Array): エフェクトを再実行する条件となる値(stateやpropsなど)のリストです。
Point

useEffectの初回実行時はレンダリングの「後」に実行します。


実行タイミングは依存配列によって変わる

useEffectで設定した関数を実行するタイミングは、第二引数の依存配列の値によって変わります。

空の配列:[]

依存配列が空の場合、初回レンダリング時(マウント時)のみ処理を実行します。

一度きりのデータ取得やイベントリスナーの初期設定などで使用します。


値あり

依存配列が空でない場合、初回レンダリング時、および指定した値が変化したタイミングで実行します。

stateなど状態の変化に合わせてデータを更新したい時に使います。


注意点:依存配列を省略しない!

依存配列を省略すると、レンダリングの度(stateやpropsが変わるたび)に毎回処理を実行します。

エラーではありませんが、こう言った処理を行うことは非常にまれです。パフォーマンス問題を起こしやすいため、通常は避けるべきです。


エフェクトの実例

例えば、ボタンがクリックされたときに、ブラウザのタイトルを更新したり、コンソールにメッセージを出力するコードは以下になります。

import { useState, useEffect } from 'react';

function ExampleComponent() {
  const [count, setCount] = useState(0);

  // 1. 基本的な useEffect の使用
  useEffect(() => {
    // ここに実行したい副作用(エフェクト)のコードを書く
    document.title = `Count: ${count}`; // 例: ブラウザのタイトルを更新
    console.log('エフェクトが実行されました');
  }, [count]); // 依存配列: count が変化した時のみ再実行

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

依存配列で [count] を指定しているので、countの値が変更されレンダリングされる度に処理を実行します。


実例:データ取得

useEffectを最も使用する可能性が高いのは外部からのデータ取得(フェッチ)です。

例えば、ユーザーIDが変更される度に、APIにそのユーザーIDを渡して、紐づく情報を取得する場合に使用します。

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  // ユーザーID(props)が変更されるたびにデータを取得するエフェクト
  useEffect(() => {
    // データを取得する関数を定義
    const fetchUserData = async () => {
      setIsLoading(true); // ロード開始

      try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error("データ取得エラー:", error);
        setUser(null);
      } finally {
        setIsLoading(false); // ロード完了
      }
    };

    fetchUserData(); // 実行

    // クリーンアップは不要(データ取得は一回きりの副作用のため)

  }, [userId]); // 依存配列: userId が変わったら再実行

  if (isLoading) {
    return <p>Loading user data...</p>;
  }

  if (!user) {
    return <p>User not found.</p>;
  }

  return (
    <div>
      <h3>{user.name}</h3>
      <p>Email: {user.email}</p>
    </div>
  );
}


クリーンアップ

クリーンアップとは何か?

useEffectを使う場合に「クリーンアップ」が必要になる場合があります。

クリーンアップとは、コンポーネントが画面から消える前や、エフェクトが再実行される前に、古い副作用の影響を完全に取り除くことです。

特に、Reactコンポーネントがブラウザの機能や外部ライブラリと連携する際に必要となります。


クリーンアップがなぜ必要か?

例えば、useEffectによってブラウザのタイマーを起動させたり、マウスの監視イベントを設定した場合、別の画面に移ってuseEffectを実行したReactコンポーネントがなくなったあとも、それらの機能が動き続けます。

既に必要がないのにブラウザの機能を使い続けると無駄にブラウザのメモリを消費したり、他のイベントがセットされたときに重複して実行したりといった意図せぬ動きをすることがあります。

これを防ぐためにクリーンアップが必要となります。

MEMO

不要になったメモリを解放せずに占有し続けることを「メモリリーク(Memory Leak)」と呼びます。

ソフトウェアの動作の不具合(バグ)の一種であり、プログラムがメモリを「漏らしている(Leak)」ように見えることからこの名前がついています。


クリーンアップの使い方

クリーンアップの使い方はとても簡単で、useEffectの処理(副作用)の最後で、returnで関数を返すだけです。

MEMO

returnで関数を返すので、クリーンアップ関数と呼びます。

useEffect(() => {
  //副作用を伴う処理

  // クリーンアップ関数をreturnで返す
  return () => {
    // クリーンアップの処理
  };
}, []); // 依存配列


クリーンアップが必要な実例

コンポーネントが画面に表示されている間だけ動くタイマーをセットする場合、そのコンポーネントがなくなった場合にクリーンアップが必要となります。

import { useState, useEffect } from 'react';

//このコンポーネントが画面から消えると、クリーンアップが走る
function TimerDisplay() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // 副作用:タイマー(外部のブラウザAPI)を設定
    const timerId = setInterval(() => {
      // 1秒ごとに実行される処理
      setSeconds(prev => prev + 1);
      console.log('タイマーが動いています...');
    }, 1000);

    // クリーンアップ関数を返す
    return () => {
      clearInterval(timerId); // 外部で動き続けているタイマーを停止する
      console.log('タイマーを停止しました!メモリリーク防止完了。');
    };
  }, []); // 依存配列が空なので、初回マウント時のみ実行

  return (
    <div>
      <h3>経過時間: {seconds}秒</h3>
    </div>
  );
}


データ取得(fetch)はクリーンアップ不要

データ取得(fetchなど)のように、一回限りの非同期処理で、後で停止したり解除したりする必要がない副作用はクリーンアップは不要です。


エフェクトは必要最低限にする

エフェクトは便利な一方で、安易に使うとバグやパフォーマンスの低下につながるため、あくまでも、外部システムと同期する場合のみ使用します。

特に以下の場合、エフェクトは不要です。

不要なケースやり方
stateやpropsから別の値を計算したいコンポーネント本体で直接計算する。
ユーザーの操作に直接応答したいイベントハンドラー(onClickonChangeなど)内で処理を実行する。
stateの変更に合わせて別のstateを更新したい可能な限り一つの状態から他の値を算出するか、イベントハンドラー内で両方の状態を同時に更新する。


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