【React】useEffectで初回のみ(1回だけ)実行させる方法と使うなと言われるアンチパターンの解説

2023年1月1日React

ReactのuseEffectを使って初回のみ(1回だけ)処理を実行する方法を実例とともに解説します。
また、useEffectの使い方を間違えるとエラーやパフォーマンス低下を招く「アンチパターン」についても詳しく紹介します。

ReactのuseEffectで初回のみ(1回だけ)実行させる方法

useEffectの第二引数に空の依存配列([])を指定することで、コンポーネントの初回マウント時(最初に画面に描画されるとき)のみ実行させることができます。

実例:setIntervalを使った時間の経過表示


こちらは、setIntervalを1秒に設定して時間を1秒ずつ進めるという単純なものです。
useEffectの中で書かないと時間の経過がおかしいことになります。
それは、setTimeという更新用関数が1秒ごとに実行されてtimeというstateが変化するので、コンポーネントが再レンダリングされてまたsetIntervalを実行してしまうためです。
このように、初回だけ実行したいというケースがあります。
以下がコードの説明です。

コードの説明

import { useEffect, useState } from "react";

const Sample = () => {
  const [time, setTime] = useState(0);
  useEffect(() => {
    window.setInterval(() => {
      setTime(prev=>prev+1);
    }, 1000)

  // クリーンアップ処理
    return () => clearInterval(timer);
  }, []);

  return (
    <>
      <p></p>
      <time>{time}</time>
      <span>秒経過</span>
    </>
  );
};

export default Sample;

コード解説

useStateで状態を管理

timeという状態を作成し、初期値は0に設定します。

useEffectで副作用を実行

setIntervalで1秒ごとにtimeを更新する処理を記述しています。
useEffectは、第一引数に処理内容を記述した関数、第二引数に依存する配列を指定します。
初回だけ実行したい場合は、第二引数の依存する配列部分を[]というようにからの配列を指定するだけで初回マウント時のみにこの処理が実行されます。

クリーンアップ処理

コンポーネントがアンマウント(画面から削除)された際にsetIntervalを解除するため、returnでクリーンアップ処理を記述しています。

注意点

肝の部分は、初回だけ実行したい処理をuseEffectの関数で実行して、第二引数に空配列を指定するというポイントです。
第二引数に空配列をいれないと、処理内容を記述した関数がuseEffectの外にある場合と同じ挙動をとるので、再レンダリングが発生して時間の進み方がおかしくなります。

useEffectを使うなと言われる避けるべきアンチパターン

useEffectは便利な反面、間違った使い方をするとバグやパフォーマンスの低下を招くことがあります。
ここではuseEffectを使わない方がいい代表的なアンチパターンを紹介します。

依存配列(第二引数)の設定ミス

useEffectの第二引数(依存配列)を正しく設定しないと、以下の問題が発生します。

無限ループ:useEffect内で更新される状態を依存配列に含め忘れると、再レンダリングのたびに無限ループが発生します。
意図しない再実行:不必要な変数を依存配列に含めると、毎回useEffectが実行され、パフォーマンスが低下します。

// 無限ループの例
useEffect(() => {
  setTime(time + 1); // timeが更新されるたびに再実行
}, [time]); // 意図しない依存

解決策:

依存配列(第二引数)を正しく設定し、更新が必要な変数のみ含めます。
useReducerやuseRefを使って状態を管理する場合、適切に分離します。

非同期処理の誤った扱い

useEffect内で非同期処理を直接記述すると、以下の問題が起きる可能性があります。

コンポーネントのアンマウント後に古い非同期処理が実行されてしまったり、
メモリリークが発生してしまいます。

// アンチパターン
useEffect(() => {
  fetchData().then(data => setData(data));
}, []);

解決策:

非同期関数をuseEffectの外部で定義し、useEffect内ではその関数を呼び出す形にします。

useEffect(() => {
  const fetchDataAsync = async () => {
    const data = await fetchData();
    setData(data);
  };

  fetchDataAsync();
}, []);

副作用が不要な場面での利用

状態の更新やロジックにuseEffectを必要以上に使用することもアンチパターンです。

例:単純な状態更新をuseEffectで行う必要はありません。

// アンチパターン
useEffect(() => {
  if (value > 10) {
    setOtherValue(value + 1);
  }
}, [value]);

// 解決策
if (value > 10) {
  setOtherValue(value + 1);
}

useEffectを使わない初回実行の方法

useEffectを使わずに初回のみ処理を実行する方法として、以下のアプローチがあります。

useRefを利用

useRefを使用して初回実行を制御できます。

import { useRef } from "react";

const Sample = () => {
  const hasRun = useRef(false);

  if (!hasRun.current) {
    console.log("初回のみ実行");
    hasRun.current = true;
  }

  return <div>初回のみ実行されます</div>;
};

export default Sample;

イベントハンドラで初回実行を管理

初回実行が必要な処理をイベントトリガーに関連付けることで、useEffectを回避できます。

まとめ

  • useEffectで初回のみ実行する場合、依存配列(第二引数)に[]を設定。
  • クリーンアップ処理を忘れない。
  • アンチパターンを避けるために、依存配列(第二引数)や非同期処理の扱いに注意する。
  • 必要に応じて、useEffectを使わない代替手段も検討する。

useEffectは便利なフックですが、適切に使うことでパフォーマンスやコードの可読性を向上させることができます。
アンチパターンも理解した上で、適切に使っていきましょう。

React

Posted by devsakaso