【React Hooks】useEffectの使い方を解説!非同期処理を扱う方法は?

当サイトでは一部リンクに広告が含まれています
Reactアイコン

React Hooks について本ブログでもよく詳しい解説をしていますが、今回はその中でも特に重要な useEffect を解説します。
useState と同じぐらいよく使われる Hooks ですので、この機会にしっかりおさえておきましょう

目次

ReactのuseEffectとは何か?

ReactのuseEffectは、関数型コンポーネントで副作用を扱うためのHookです。
副作用とは、関数の実行によって発生する外部の変更のことを指します。
例えば、APIからデータを取得する処理や、DOMの変更などが副作用にあたります。

useEffectは、コンポーネントがレンダリングされた後に実行されます。
これは、コンポーネントが画面に表示された後に何かを行いたいときに便利です。

例えば、以下のコードでは、コンポーネントがレンダリングされた後にこんにちは、世界!をコンソールに出力します。

useEffect(() => {
  console.log('こんにちは、世界!');
}, []);

この例だけではuseEffectのメリットが分かりにくいかもしれません。
そこで、APIからデータを取得するより具体的な例を見てみましょう。

useEffect(() => {
  fetch('https://api.example.com/items')
    .then(res => res.json())
    .then(
      (result) => {
        setItems(result);
      },
      (error) => {
        setError(error);
      }
    )
}, []);

上記の例では、useEffectを使用してAPIからデータを取得しています。
第2引数の空配列により、この副作用はコンポーネントがマウントされた時に一度だけ実行されます。

このように、useEffectを使うことで、クラスコンポーネントのライフサイクルメソッドを関数コンポーネントで扱うことができます。

ライフサイクルメソッドとは、コンポーネントの生成から破棄までの間に発生するイベントのことです。
例えば、コンポーネントが生成された時に実行されるcomponentDidMountや、コンポーネントが破棄される時に実行されるcomponentWillUnmountなどがあります。

useEffectの基本的な使い方

useEffectは、コンポーネントの初回レンダリング後と、依存配列(後述)にある何かが変更された時に実行されます。
以下の例では、countが変更されるたびに、その新しい値がコンソールに出力されます。

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

useEffect(() => {
  console.log(`カウントは現在 ${count} です`);
}, [count]);

依存配列とは何か?

useEffectの第二引数として「依存配列」を提供します。
この配列は、useEffectが監視する値のリストです。
これらの値のうちの1つが変わるたびに、useEffect内のコードが再度実行されます。

ただし、依存配列が空の場合、useEffectはコンポーネントがマウントされた時に一度だけ実行されます。
そのため、画面が表示された時に一度だけ実行したい処理を記述する場合は、依存配列を空にすると良いでしょう。

useEffect(() => {
  // 画面が表示された時に一度だけ実行したい処理
  console.log('こんにちは、世界!');
}, []);

useEffectで非同期処理を行う

useEffectは、データのフェッチなどの非同期操作を行うときによく使われます。

ただし、useEffectのコールバック自体は同期的であるため、非同期関数を直接呼び出すことはできません。
そのため、通常は非同期関数を定義してからそれを呼び出します。

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

  fetchData();
}, []);

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

useEffectの内部から関数を返すことで、副作用の「クリーンアップ」を行うことができます。
このクリーンアップは、次の副作用が開始する前またはコンポーネントがアンマウントされるときに実行されます。

以下のコードでは、タイマーを設定し、そのタイマーをクリーンアップするための関数を返しています。

useEffect(() => {
  const timerID = setTimeout(() => {
    console.log('1秒経過しました');
  }, 1000);

  return () => {
    clearTimeout(timerID);
  };
}, []);

上記の例では、タイマーをクリーンアップするためにclearTimeoutを使用しています。
これがないと、コンポーネントがアンマウントされた後もタイマーが動き続けてしまい、思わぬバグの原因になります。

他の例も見てみましょう。

例えば、APIからデータを取得する処理を行う際に、その処理が終わる前にユーザーがページを離れてしまった場合、APIへのリクエストをキャンセルするために使うことができます。

useEffect(()

 => {
  const controller = new AbortController();
  const signal = controller.signal;

  fetch('https://api.example.com/items', { signal })
    .then(res => res.json())
    .then(
      (result) => {
        setItems(result);
      },
      (error) => {
        setError(error);
      }
    )

  return () => {
    controller.abort();
  };
}, []);

上記の例では、AbortControllerを使用してAPIへのリクエストをキャンセルしています。
これにより、ユーザーがページを離れた後もAPIへのリクエストが続くことを防ぐことができます。

useEffectの注意点

ループや条件文内での使用を避ける

ReactのHookはトップレベルで呼び出す必要があります。
ループ、条件、入れ子になった関数の中でHookを呼び出すことは避けましょう。

例えば、以下のようなコードは動作しません。

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

  if (count === 0) {
    useEffect(() => {
      console.log('カウントは現在 0 です');
    });
  }

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>増加</button>
    </div>
  );
}

上記の例では、countが0の時にのみuseEffectを呼び出そうとしていますが、React Hooks の仕様上、これは動作しません。

依存配列に関数やオブジェクトを含める際の注意

ReactのuseEffectを使う際、依存配列に関数やオブジェクトを含めることがあります。しかし、これには注意が必要です。なぜなら、関数やオブジェクトは、コンポーネントが再レンダリングされるたびに「新しい参照」を持つからです。

「新しい参照」とは何かというと、JavaScriptでは関数やオブジェクトはそれぞれ一意の場所(参照)に保存されます。そして、コンポーネントが再レンダリングされると、その中で定義された関数やオブジェクトは新たな場所に保存され、それが「新しい参照」になります。

これが問題となるのは、useEffectの依存配列に関数やオブジェクトを含めた場合です。依存配列に含まれる値が変更されると、useEffect内のコードが再実行されます。しかし、関数やオブジェクトが新しい参照を持つたびに、それが「変更」とみなされ、useEffectが再実行されてしまいます。

例えば、以下のコードを見てみましょう。

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

  const increment = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log('カウント:' + count);
  }, [increment]);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={increment}>増加</button>
    </div>
  );
}

このコードでは、increment関数がuseEffectの依存配列に含まれています。しかし、ユーザーが「増加」ボタンをクリックしてcountが増えるたびに、increment関数は新しい参照を持ちます。その結果、useEffect内のconsole.logが毎回実行されてしまいます。

これは、console.logが頻繁に実行されるだけでなく、もしuseEffect内で重い処理(例えば、APIからのデータ取得)を行っていた場合、パフォーマンスに影響を及ぼす可能性があります。

この問題を解決するためには、useCallbackを使ってincrement関数をメモ化します。これにより、countが変更されない限り、increment関数の参照は変わらず、useEffectは意図したタイミングでのみ実行されます。

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

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  useEffect(() => {
    console.log('カウント:' + count);
  }, [increment]);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={increment}>増加</button>
    </div>
  );
}

このように、useEffectの依存配列に関数やオブジェクトを含める際は、その影響を理解し、必要に応じてuseCallbackを使って関数をメモ化することが重要です。

React を効率的に身につける勉強法は?

React には興味があっても、プログラミング学習をはじめたばかりであれば、以下のような疑問を持つかもしれません。

  • Reactエンジニアとして活躍するために何をすればいい?
  • 何から勉強すればいいんだろう?
  • 絶対に転職を成功させるには?

React は習得が比較的難しいため、勉強法の選び方を間違うと時間・労力・お金が無駄になってしまいます…。

そのため、こちらの記事を参考に、ご自身に最適な学習方法を選んでみませんか?

目次