React学習 React文法

【React Hooks基礎】useEffectを理解する

本記事ではReact Hooksの一つのuseEffect()について使い方を詳細に解説いたします。

少しでも皆さんの理解につながれば嬉しいです。

では早速解説を始めていきます。

useEffect()はコンポーネント内で副作用を実現する

useEffect()はコンポーネント内で副作用を実現するために使用されます。

またuseEffect()の特徴はコンポーネントが描画された後に実行され、特定の値の状態の変化を監視することができます。

副作用とは何か?

副作用とはコンポーネントが描画されるときに、その描画に対して外部状態を変更すること指します。

外部状態の変更とは例えばタイマー、DOM操作、HTTPリクエストの送信、ブラウザストレージへのアクセスなどがあります。

Reactの本来の役割は、UIの描画と各要素の状態(値)を管理してユーザーがフォーム入力をする際などに最適な挙動をする、必要に応じて再レンダリングすることです。

一方、副作用とはReactの本来の役割以外にアプリで発生しうることを指します。

例えばHTTPリクエストなどでサーバーからの情報の取得は、Reactで描画した後に完了します。

したがって「HTTPリクエストなどでサーバーからの情報の取得」が副作用で、これを実現させるのがuseEffect()です。

構文

useEffect()の基本構文です。

useEffect(実行する副作用の関数, [管理する値の配列])

useEffect()は2つの引数をとります。

第一引数は「実行する副作用の関数」第二引数には「管理する値の配列」が入り第二引数に書いた値に変更が加わると第一引数が実行されます。

useEffect()の実行タイミングは一番最後

useEffect()は対象のコンポーネントの再計算が終わり再レンダリング時の最後に実行されます。

次の例を見てください。

const ReactDev = () => {
  console.log('useEffectの前');

  useEffect(() => {
    console.log('useEffectの中');
  },[]);

  console.log('useEffectの後');
};

この実装の実行結果はこちらです。

このように二番目に書いたuseEffect()が最後に出力されています。

第二引数は3パターンある

先程、useEffect()の構文を紹介しました。

useEffect(実行する副作用の関数, [管理する値の配列])

第二引数は合計3パターンの書き方があります。

それぞれで第一引数に設定した関数の実行条件が異なります。

  • 管理する値の配列を入れる
  • 何も書かない
  • 空の[]配列を入れる

それぞれ解説していきます。

①管理する配列の値を入れるとその値が変わるたびに実行

const ReactDev = () => {
  const [enteredTitle, setEnteredTitle] = useState('');
  const [enteredText, setEnteredText] = useState('');

  useEffect(() => {
    console.log('useEffect実行!');
  }, [enteredTitle]);

  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };
  const textAdd = (e) => {
    setEnteredText(e.target.value);
  };

  return (
    <div className="container">
      <p>テキストを入力してください</p>
      <form>
        <input type="text" onChange={titleAdd} />
        <input type="text" onChange={textAdd} />
      </form>
    </div>
  );
};

第二引数で[enteredTitle]を設定しており、テキストが入力されるたびにenteredTitleが更新されるので「useEffect実行!」が出力されます。

2つ目のinputタグに入力してもテキストが実行され無いのはuseEffect()がenteredTextを監視していないからです。

第二引数に[enteredTitle, enteredText]の両方記述するとどちらに入力しても実行されます。↓

※ちなみに一番最初に「useEffect実行!」が出ているのはページを読み込んだ時に実行されたものです。

②何も書かなければレンダリングの度に実行

const ReactDev = () => {
  const [enteredTitle, setEnteredTitle] = useState('');
  const [mailValid, setMailValid] = useState(false);

  useEffect(() => {
    console.log('useEffect発動');
  });
  
  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };

  return (
    <div className="container">
      <p>テキストを入力してください</p>
      <form>
        <input type="text" onChange={titleAdd} />
      </form>
    </div>
  );
};

第二引数を設定しないとテキスト入力=レンダリングのたびに「useEffect発動」が出てきてしまいます。

今回の例はsetEnteredTitle()の実行によって再レンダリングされるのでこのレンダリングのたびにuseEffect()が実行され「useEffect発動」出力されています。

③空の配列[]を入れると最初のレンダリングの1回だけ実行

const ReactDev = () => {
  const [enteredTitle, setEnteredTitle] = useState('');
  const [mailValid, setMailValid] = useState(false);

  useEffect(() => {
    console.log('useEffect発動');
  }, []);

  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };

  return (
    <div className="container">
      <p>テキストを入力してください</p>
      <form>
        <input type="text" onChange={titleAdd} />
      </form>
    </div>
  );
};

リロード後に「useEffect発動」が出てきて、それ以降はテキストを打っても実行されません。

副作用関数の直前に実行されるクリーンアップ関数

useEffect()にはもう一つ重要な要素があります。

それはクリーンアップ関数です。

クリーンアップ関数はuseEffect()内の第一引数と同じ階層に書き必ずreturnで関数を返すようにします。

クリーンアップ関数はuseEffect()に渡した副作用関数が実行される直前に実行されます。

const ReactDev = () => {
  const [enteredTitle, setEnteredTitle] = useState('');

  useEffect(() => {
    console.log('useEffect実行!');

    return () => {
      console.log('クリーンアップ関数実行!');
    };
  }, [enteredTitle]);

  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };

  return (
    <div className="container">
      <p>テキストを入力してください</p>
      <form>
        <input type="text" onChange={titleAdd} />
      </form>
    </div>
  );
};

テキストを打つたびに、「クリーンアップ関数実行!」→「useEffect実行!」の順番で出力されます。

このことで得られるメリットはなんでしょう?

クリーンアップ関数を使うことで、新しい副作用が実行される前に、前の副作用が行った処理を取り消すことができます。

↑これがかなり重要なポイントです。

では実際にどのようにクリーンアップ関数を使うかを見てみましょう。

クリーンアップ関数の活用例

例えば、フォーム入力で文字数をカウントする実装をするとしましょう。

こんな感じです。

const ReactDev = () => {
  const [enteredTitle, setEnteredTitle] = useState('');
  const [textCount, setTextCount] = useState(0);

  useEffect(() => {
    setTextCount(enteredTitle.trim().length);
  }, [enteredTitle]);

  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };

  return (
    <div className="container">
      <p>テキストを入力してください</p>
      <form>
        <input type="text" onChange={titleAdd} />
      </form>
      <p>文字数:{textCount}</p>
    </div>
  );
};

今の状態ではテキストを打つたびに文字数がカウントされています。

テキスト入力のたびに計算するのではなくテキストを入力し始めて数秒後にカウントするような仕様に変更したくなったとします。

タイマー機能で2秒後に文字数を出力するようにします。
変更部分はこちらです。

useEffect(() => {
    setTimeout(() => {
      setTextCount(enteredTitle.trim().length);
    }, 2000);
  }, [enteredTitle]);

  const titleAdd = (e) => {
    setEnteredTitle(e.target.value);
  };

これで文字数のカウントは2秒後に行われるようになりました。

しかし、これでは2秒後に遅れて0から5までのカウントされるようになっただけです。

実現したい実装は2秒後にそれまで入力した文字数をパッと表示させたいのです。

ここでクリーンアップ関数が登場です。

useEffect(() => {
    const timer = setTimeout(() => {
      setTextCount(enteredTitle.trim().length);
    }, 2000);

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

これで完成です。

何が起きているかの解説をします。

まずテキストを1文字打つと、クリーンアップ関数が実行されます。

しかしこの時点では、クリーンアップ関数であるclearTimeoutが実行されますが挙動自体には何も影響しません。

クリーンアップ関数実行後に副作用関数であるタイマーが実行されます。

2秒以内にテキスト2文字目を打ち込むと、またクリーンアップ関数が実行され、直前の副作用関数のタイマーと文字出力をキャンセルしてくれます。

その後、副作用関数が実行されタイマーが実行されます。

2秒以内にテキスト3文字目を打ち込むと、またクリーンアップ関数が実行され、直前の副作用関数のタイマーと文字出力をキャンセルしてくれます・・・

つまりテキスト入力のたびにクリーンアップ関数が直前の副作用関数のタイマーと文字出力をキャンセルし、入力が完了すると最後に一回だけ副作用関数のタイマーと文字出力が行われるということです。

もちろんテキスト入力に2秒以上かかると、途中で文字数が表示されてしまいます。

最後に

今回はuseEffect()の解説をしました。

少しでもこれからプログラミングを学ぼうと考えている方の為になる発信をできれば思いますので、温かい目で見てもらえると嬉しいです。

私がReactを学んでいるUdemyの講座が下記です。48時間と長いですが基本から応用までかなり解説してくれているので興味があればぜひチェックしてみてください😌

ベストセラー取得

-React学習, React文法