React

React useEffect入門|副作用とstateの変化を処理する

ReactのuseEffectは、コンポーネントのレンダリング後に副作用(データの取得、DOM操作、タイマーの設定など)を実行するためのフックです。クラスコンポーネントのcomponentDidMountcomponentDidUpdateに相当する機能を、関数コンポーネントで実現できます。ここでは、useEffectの基本的な使い方から実践的なパターンまでを解説します。

基本的な使い方

sample.jsx
import React, { useState, useEffect } from 'react';

function TimerCounter() {
  const [count, setCount] = useState(0);
  const [isRunning, setIsRunning] = useState(false);
  
  // タイマーの設定と解除を行うuseEffect
  useEffect(() => {
    let timerId = null;
    
    // タイマーが実行中の場合だけ設定
    if (isRunning) {
      // 1秒ごとにカウントを増やす
      timerId = setInterval(() => {
        setCount(prevCount => prevCount + 1);
      }, 1000);
      
      console.log('タイマーを開始しました');
    }
    
    // クリーンアップ関数:タイマーを解除
    return () => {
      if (timerId) {
        clearInterval(timerId);
        console.log('タイマーを停止しました');
      }
    };
  }, [isRunning]); // isRunningが変わったときだけ実行
  
  // スタート/ストップボタンのクリックハンドラー
  const toggleTimer = () => {
    setIsRunning(!isRunning);
  };
  
  // リセットボタンのクリックハンドラー
  const resetTimer = () => {
    setCount(0);
    setIsRunning(false);
  };
  
  return (
    <div style={{ 
      textAlign: 'center', 
      margin: '20px', 
      padding: '20px', 
      borderRadius: '8px',
      boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
      maxWidth: '400px'
    }}>
      <h2 style={{ color: '#2980b9' }}>タイマーカウンター</h2>
      
      <div style={{ 
        fontSize: '48px', 
        fontWeight: 'bold',
        margin: '20px 0',
        color: isRunning ? '#e74c3c' : '#333'
      }}>
        {count}
      </div>
      
      <div>
        <button 
          onClick={toggleTimer}
          style={{
            padding: '10px 20px',
            margin: '0 10px',
            backgroundColor: isRunning ? '#e74c3c' : '#2ecc71',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontSize: '16px'
          }}
        >
          {isRunning ? '停止' : '開始'}
        </button>
        
        <button 
          onClick={resetTimer}
          style={{
            padding: '10px 20px',
            backgroundColor: '#3498db',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontSize: '16px'
          }}
        >
          リセット
        </button>
      </div>
      
      <p style={{ marginTop: '20px', color: '#7f8c8d' }}>
        {isRunning 
          ? '実行中: タイマーは1秒ごとにカウントを増やしています' 
          : '停止中: 「開始」ボタンをクリックしてタイマーを開始してください'}
      </p>
    </div>
  );
}

export default TimerCounter;

説明

Step 1useEffectの基本

useEffectは副作用(サイドエフェクト)を扱うためのHookです。データの取得やDOM操作など、レンダリング以外の処理を実行するために使います。

useEffectを使うには、まずReactからインポートします:

import React, { useEffect } from 'react';

基本的な構文は以下の通りです:

useEffect(() => { // 実行したい副作用のコード // クリーンアップ関数(オプション) return () => { // コンポーネントのアンマウント時やeffectの再実行前に実行される }; }, [依存配列]); // 依存配列を指定

Step 2依存配列の仕組み

useEffectの第二引数である依存配列は、effectの実行タイミングを制御します:

  • 空の配列 []:コンポーネントがマウントされた時だけ実行
  • 変数を含む配列 [count, name]:指定した変数が変更されるたびに実行
  • 依存配列なし:レンダリングごとに毎回実行
// マウント時に一度だけ実行 useEffect(() => { console.log('コンポーネントがマウントされました'); }, []); // countが変更されるたびに実行 useEffect(() => { console.log('countが変更されました:', count); }, [count]); // レンダリングごとに毎回実行 useEffect(() => { console.log('レンダリングが完了しました'); });

Step 3タイトルを更新する例

useStateとuseEffectを組み合わせたシンプルな例として、カウンターの値に応じてページのタイトルを更新するコードを見てみましょう:

import React, { useState, useEffect } from 'react'; function CounterWithTitle() { const [count, setCount] = useState(0); // countが変更されるたびにタイトルを更新 useEffect(() => { // ブラウザのタブに表示されるタイトルを変更 document.title = `カウント: ${count}`; console.log('タイトルを更新しました:', count); }, [count]); // countを依存配列に指定 return ( <div> <h1>カウント: {count}</h1> <button onClick={() => setCount(count + 1)}> 増やす </button> </div> ); }

このコードでは:

  • countの状態が変わるたびにuseEffectが実行される
  • ブラウザのタブのタイトルがカウント値に応じて更新される
  • 依存配列に[count]を指定しているので、count変数の値が変わった時だけ実行される

Step 4クリーンアップ関数の例

ウィンドウサイズを監視する例で、クリーンアップ関数の使い方を見てみましょう:

import React, { useState, useEffect } from 'react'; function WindowSizeTracker() { const [windowWidth, setWindowWidth] = useState(window.innerWidth); useEffect(() => { // ウィンドウサイズが変更されたときに実行される関数 const handleResize = () => { setWindowWidth(window.innerWidth); }; // イベントリスナーを追加 window.addEventListener('resize', handleResize); // クリーンアップ関数:イベントリスナーを削除 return () => { window.removeEventListener('resize', handleResize); console.log('イベントリスナーを削除しました'); }; }, []); // マウント時にのみ実行 return ( <div> <h1>現在のウィンドウ幅: {windowWidth}px</h1> </div> ); }

このコードでは:

  • マウント時にウィンドウサイズの変更を監視するリスナーを設定
  • サイズが変更されるとstateが更新され、表示が更新される
  • クリーンアップ関数でコンポーネントがアンマウントされる際にリスナーを削除(メモリリーク防止)
ポイント

重要: イベントリスナーやタイマー、サブスクリプションなどを設定する場合は、必ずクリーンアップ関数でそれらを解除しましょう。解除しないとメモリリークの原因になります。

また、useEffectの依存配列には、effect内で使用しているすべての変数を含めるのが原則です。もし依存配列を空のままにしたい場合は、変数をeffectの中で宣言するか、useCallbackなどの他のHookを使用することを検討してください。

まとめ

  • useEffectはレンダリング後に副作用を実行するためのフック
  • 依存配列[]を指定するとマウント時のみ実行される
  • 依存配列に変数を含めると、その変数の変更時に再実行される
  • クリーンアップ関数を返すことで、タイマーやイベントリスナーを解除できる
  • 依存配列を省略すると毎回レンダリング後に実行されるため注意が必要
  • 非同期処理(API呼び出しなど)はuseEffect内で行うのが基本パターン