【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エンジニアとして活躍するために何をすればいい?
  • 何から勉強すればいいんだろう?
  • 絶対に転職を成功させるには?

そんな方は、転職サポートまでおまかせできるプログラミングスクールも検討してみてもいいかもしれませんね。

無料で体験レッスンを1週間受けられるスクールや、給付金により実質15万円以下で6ヶ月も学べるスクールもありますので、まずは気軽に無料カウンセリングを受けてみてはいかがでしょうか?

目次