Code Journey

30代未経験からプログラミング挑戦中(追うものは追われる者に勝る)

【React】基本的なuseEffectの使い方と実用例

はじめに

前提

まず前提として、この記事を書いているのは未経験からプログラミングに挑戦中の者になります。学習はフィヨルドブートキャンプに参加しながら取り組んでいて、現役のエンジニアの方からレビューをもらいながら進めています。

現在の学習状況は以下を随時更新しているので興味ある方はご覧ください。

hirano-vm4.hatenablog.com

そのため、学んだことを不定期に初学者向けにアウトプットしています。

useEffectとは

ReactのuseEffectは、関数コンポーネント内で副作用(side effects)を扱うためのフック(Hooks)です。

副作用とは、データの取得、DOMの変更など、コンポーネントレンダリング結果に影響を与える操作のことを指します。

たとえば、DOMの更新後に特定の操作を実行したり、外部APIからデータをフェッチしたり、イベントリスナーを設定したりする場合などに利用されます。

基本的な使い方

// まずインポートする
import React, { useState, useEffect } from 'react';

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

  // コンポーネントがレンダリングされるたびに実行されます
  useEffect(() => {
    document.title = `クリック回数: ${count} 回`;
  });

  return (
    <div>
      <p>クリック回数: {count} 回</p>
      <button onClick={() => setCount(count + 1)}>
        クリック
      </button>
    </div>
  );
}

この例では、useEffect内でドキュメントのタイトルを更新しています。useEffectに渡された関数は、コンポーネントの初回レンダリング後と、その後の各レンダリング後に実行されます。

依存配列(Dependency Array)

useEffect第二引数に空の配列([])を渡すと、副作用はコンポーネントの初回レンダリング時にのみ実行され、その後は実行されません。

配列内に特定の状態やプロップスを指定すると、その値が変更された時のみ副作用が実行されます。

useEffect(() => {
  console.log('この副作用は初回レンダリング時にのみ実行されます');
}, []); // 空の依存配列

useEffect(() => {
  console.log(`countが更新されました: ${count}`);
}, [count]); // countのみを依存配列に指定

依存配列の例

0. 第二引数に何も指定しなかった場合

依存配列を省略すると、コンポーネントレンダリングされるたび(状態やpropsが更新されるたび)に副作用が実行されます。

useEffect(() => {
  // この副作用はコンポーネントのレンダリング後に毎回実行されます。
});

ただし、多くの場合、パフォーマンスの観点から無駄な再実行を避けるために、依存配列を適切に設定することが推奨されるようです。

1. 空の配列を指定した場合

先ほど記述したように第2引数に空の配列を設定した場合は、初回のレンダリング後に1回だけ実行されます。

useEffect(() => {
  console.log('コンポーネントがマウントされた時に一度だけ実行されます');
}, []);

この場合、副作用はコンポーネントがマウントされた時に一度だけ実行され、アンマウント時や値の更新時には実行されません。

APIからデータを取得する、イベントリスナーを設定する、タイマーを設定するなど、コンポーネントのライフサイクル中に一度だけ行いたい操作に適しているようです。

2. 依存配列に値を指定した場合

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

useEffect(() => {
  console.log(`countが更新されました: ${count}`);
}, [count]);

この場合、countの値が変更されるたびに副作用が実行されます。count以外の状態が変更されても副作用は実行されません。

useEffectの実用例

基本的効果は分かりましたが、どんな場面でどう使うのかがイメージがつかなかったので少し調べてみました。

実用例①データフェッチング

APIからデータをフェッチしてコンポーネントに表示する場合、useEffectを使用してデータの取得を行います。これにより、コンポーネントが画面に表示された直後にデータがロードされます。

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); // 空の依存配列を渡すことで、コンポーネントのマウント時にのみ実行されます

  return (
    <div>
      {data ? <div>{data.name}</div> : 'Loading...'}
    </div>
  );
}

実用例②イベントリスナーの設定とクリーンアップ

ウィンドウサイズの変更やキーボードイベントなど、特定のイベントに対するリスナーを設定する場合にuseEffectが役立つ。

また、コンポーネントのアンマウント時にこれらのイベントリスナーをクリーンアップ(削除)することも重要。

function App() {
  useEffect(() => {
    const handleResize = () => console.log('Window resized');
    window.addEventListener('resize', handleResize);

    // クリーンアップ関数
    return () => window.removeEventListener('resize', handleResize);
  }, []); // 空の依存配列

  return <div>Hello World</div>;
}

実用例③外部ライブラリの統合

DOM要素に対して外部ライブラリ(例えば、チャートライブラリやスライダーライブラリ)を適用する場合、useEffect内でライブラリの初期化コードを実行します。

レンダリング後にDOMが準備完了していることを保証するためにuseEffectを使用します。

function Chart() {
  useEffect(() => {
    // Chart.jsの初期化など
    const ctx = document.getElementById('myChart').getContext('2d');
    new Chart(ctx, {
      // Chart.jsの設定
    });
  }, []);

  return <canvas id="myChart"></canvas>;
}

実用例④ タイマーの設定

setTimeoutsetIntervalを使って、一定時間後に何かを実行する。

useEffect(() => {
  const timerId = setTimeout(() => {
    console.log('このメッセージは3秒後に表示されます');
  }, 3000);

  return () => clearTimeout(timerId); // タイマーをクリア
}, []); // 空の依存配列を使用

副作用が特定の条件(初回マウント時、特定の依存値の変更時など)でのみ実行されるように、依存配列を適切に使用することにより、不要な副作用の実行を防ぎ、パフォーマンスを最適化することができる。

使用するにあたって注意すべき点

使うにあたって注意すべき点についても調べてみました

①useEffect に渡すコールバック関数を Promise にしてはいけない

useEffectに非同期関数を直接渡すことは推奨されていないとのことです。

これは、useEffect のコールバック関数が戻り値としてクリーンアップ関数を期待しているのが理由のようです。非同期関数は Promise を返すため、そのため期待した動きにならなくなります。

非同期処理をuseEffect内で扱う正しい方法は、コールバック関数内で非同期関数を定義し、それを直接呼び出すことです。

間違った使用例:

useEffect(async () => {
  const response = await fetch('https://api.example.com/data');
  console.log(response);
}, [query]);

上記のコードは、useEffect のコールバック関数として非同期関数を直接渡している。結果、この非同期関数は Promise を返すため、useEffect の期待するクリーンアップ関数を返していない例となる。

正しい使い方は、useEffect のコールバック関数の内部に非同期関数を定義し、呼び出すようにしてあげる。

正しい使用例:

import React, { useEffect } from 'react';

const SimpleExample = () => {
  useEffect(() => {
    // 非同期関数を定義
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.error('Fetching data failed', error);
      }
    };

    // 非同期関数を呼び出し
    fetchData();
  }, []); // 空の依存配列を指定して、コンポーネントのマウント時に1回だけ実行されるようにする

  return <div>Check the console for data.</div>;
};

このコードでは、useEffect フックの内部で非同期関数 fetchData を定義し、即座にそれを呼び出している。

依存配列 [] が空なので、この useEffect フックはコンポーネントの初回マウント時に1回だけ実行される。

fetchData 関数内では、非同期にデータをフェッチし、成功した場合は結果をコンソールに出力し、エラーが発生した場合はエラーメッセージをコンソールに出力されます。

②不必要な再レンダリングの引き起こし

useEffect内でステートを更新すると、そのコンポーネントが再レンダリングされます。依存配列を正しく設定しないと、無限ループに陥ることがある。

誤った例:

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

  useEffect(() => {
    setCount(count + 1); // これにより再レンダリングがトリガーされる
    // 依存配列に何も指定していないため、再レンダリングの度に実行される
  });
}

使っていく中でまた、気づいたことがあれば追記していきたいと思います!

さいごに

Reactのチュートリアルで学習を進めていく中で、いまいちどんな場面でuseEffectを使うの分からなかったので調べたので、ブログにもまとめてみました。

私も学習中のみなので、レベルが上げて、また新しい発見があれば発信していきたいと思います。