AI時代のReact入門 第11回:useEffectの基礎

「APIからデータを取得したいけど、どこに書けばいいの?」 「画面が表示されるたびに処理を実行したいんだけど…」

Reactで開発していると、「コンポーネントの表示」以外のことをしたくなる場面が出てきますよね。そんなときに使うのがuseEffectです。

今回は、React Hooksの重要な機能「useEffect」の基礎について、実践的なコード例を交えながら解説していきます。この記事を読めば、データの取得やイベントの購読など、副作用を適切に処理できるようになり、より本格的なアプリケーション開発ができるようになるはずです。

useEffectの基礎:副作用の処理を理解しよう

「APIからデータを取得したいけど、どこに書けばいいの?」 「画面が表示されるたびに処理を実行したいんだけど…」

Reactで開発していると、「コンポーネントの表示」以外のことをしたくなる場面が出てきますよね。そんなときに使うのがuseEffectです。

今回は、React Hooksの重要な機能「useEffect」の基礎について、実践的なコード例を交えながら解説していきます。この記事を読めば、データの取得やイベントの購読など、副作用を適切に処理できるようになり、より本格的なアプリケーション開発ができるようになるはずです。

useEffectとは何か

useEffectは、コンポーネントの「副作用」を処理するためのHookです。副作用とは、レンダリング以外の処理のことで、例えば:

  • APIからのデータ取得
  • イベントリスナーの設定
  • タイマーの設定
  • ローカルストレージの操作

などが該当します。

useEffectの基本的な使い方

jsx
import { useEffect } from 'react';

function ExampleComponent() {
  useEffect(() => {
    // 実行したい副作用の処理
    console.log('コンポーネントがマウントされました');

    // クリーンアップ関数(必要な場合)
    return () => {
      console.log('コンポーネントがアンマウントされます');
    };
  }, []); // 依存配列

  return <div>Example</div>;
}

実践的な使用例

1. データ取得の実装

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

const DataFetching = () => {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        // 実際のAPIエンドポイントに置き換えてください
        const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
        if (!response.ok) throw new Error('データの取得に失敗しました');
        const data = await response.json();
        setPosts(data);
        setLoading(false);
      } catch (err) {
        setError(err.message);
        setLoading(false);
      }
    };

    fetchPosts();

    // クリーンアップ関数
    return () => {
      // コンポーネントのアンマウント時やre-fetch時にキャンセルしたい処理があればここに書く
    };
  }, []); // 空の依存配列 = マウント時のみ実行

  if (loading) {
    return (
      <div className="p-4">
        <div className="animate-pulse space-y-4">
          {[1, 2, 3].map((n) => (
            <div key={n} className="h-24 bg-gray-200 rounded"></div>
          ))}
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-4">
        <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
          {error}
        </div>
      </div>
    );
  }

  return (
    <div className="p-4 space-y-4">
      {posts.map(post => (
        <div key={post.id} className="border rounded p-4 hover:shadow-md transition-shadow">
          <h2 className="text-xl font-bold mb-2">{post.title}</h2>
          <p className="text-gray-600">{post.body}</p>
        </div>
      ))}
    </div>
  );
};

export default DataFetching;

2. ウィンドウサイズの監視

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

const WindowSizeTracker = () => {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  const [breakpoint, setBreakpoint] = useState('');

  useEffect(() => {
    // ウィンドウサイズの変更を監視する関数
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });

      // ブレークポイントの設定
      if (window.innerWidth < 640) {
        setBreakpoint('sm');
      } else if (window.innerWidth < 768) {
        setBreakpoint('md');
      } else if (window.innerWidth < 1024) {
        setBreakpoint('lg');
      } else {
        setBreakpoint('xl');
      }
    };

    // イベントリスナーを登録
    window.addEventListener('resize', handleResize);

    // クリーンアップ関数
    return () => {
      // イベントリスナーを解除
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 空の依存配列 = マウント時のみ実行

  return (
    <div className="p-4 border rounded shadow-sm">
      <h2 className="text-xl font-bold mb-4">ウィンドウサイズ</h2>
      
      <div className="space-y-4">
        <div className="grid grid-cols-2 gap-4">
          <div className="p-3 bg-blue-100 rounded">
            <p className="font-medium">幅</p>
            <p className="text-xl">{windowSize.width}px</p>
          </div>
          <div className="p-3 bg-green-100 rounded">
            <p className="font-medium">高さ</p>
            <p className="text-xl">{windowSize.height}px</p>
          </div>
        </div>
        
        <div className="p-3 bg-purple-100 rounded">
          <p className="font-medium">現在のブレークポイント</p>
          <p className="text-xl">{breakpoint}</p>
        </div>
      </div>
      
      <div className="mt-4 text-sm text-gray-600">
        <p>ブラウザのウィンドウサイズを変更してみてください。</p>
        <p>値がリアルタイムで更新されます。</p>
      </div>
    </div>
  );
};

export default WindowSizeTracker;

依存配列の理解

useEffectの依存配列は、副作用の実行タイミングを制御する重要な要素です。

1. 空の依存配列 []

jsx
useEffect(() => {
  console.log('マウント時のみ実行');
}, []);
  • コンポーネントがマウントされた時のみ実行
  • データの初期取得などに使用

2. 依存配列なし

jsx
useEffect(() => {
  console.log('毎回のレンダリングで実行');
});
  • レンダリングのたびに実行
  • パフォーマンスに注意が必要

3. 値を指定した依存配列

jsx
useEffect(() => {
  console.log('countが変更された時に実行');
}, [count]);
  • 指定した値が変更された時のみ実行
  • 必要な依存関係は必ず含める

クリーンアップの重要性

クリーンアップ関数は、コンポーネントのアンマウント時やre-render前に実行される重要な機能です。

イベントリスナーのクリーンアップ

jsx
useEffect(() => {
  const handler = () => {
    console.log('スクロール');
  };
  
  window.addEventListener('scroll', handler);
  
  // クリーンアップ関数
  return () => {
    window.removeEventListener('scroll', handler);
  };
}, []);

タイマーのクリーンアップ

jsx
useEffect(() => {
  const timer = setInterval(() => {
    console.log('1秒経過');
  }, 1000);
  
  // クリーンアップ関数
  return () => {
    clearInterval(timer);
  };
}, []);

よくある間違いと解決策

1. 依存配列の指定ミス

jsx
// ❌ 依存配列が不足している
useEffect(() => {
  setTotal(count * price);
}, [count]); // priceも依存配列に含めるべき

// ✅ 正しい依存配列の指定
useEffect(() => {
  setTotal(count * price);
}, [count, price]);

2. 非同期関数を直接渡す

jsx
// ❌ 非同期関数を直接効果として指定
useEffect(async () => {
  const data = await fetchData();
}, []);

// ✅ 内部で非同期関数を定義
useEffect(() => {
  const fetchData = async () => {
    const data = await fetch();
  };
  fetchData();
}, []);

3. 無限ループの発生

jsx
// ❌ 無限ループが発生する
useEffect(() => {
  setCount(count + 1);
}, [count]);

// ✅ 必要な場合のみ更新
useEffect(() => {
  if (shouldUpdate) {
    setCount(count + 1);
  }
}, [count, shouldUpdate]);

まとめ

useEffectについて学んだ内容を整理しましょう:

  • useEffectは副作用を処理するためのHook
  • 依存配列で実行タイミングを制御できる
  • クリーンアップ関数でリソースの解放が必要
  • 適切な依存関係の管理が重要
  • 非同期処理は内部で定義する

次回は「APIとの連携」について学んでいきます。今回学んだuseEffectを使って、実際のAPIとの通信処理を実装する方法を解説していきます。