1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React】useEffectの使い方 💡

Posted at

useEffectとは

React Hooksの一つで、コンポーネントがレンダリングされたときに副作用を実行するために使用されます。

コンポーネントはJSXを構築する場所であってJSXの構築に直接関係のない処理は全て副作用として扱われます。
それらの副作用は純粋関数の観点などからuseEffectやイベントハンドラ内に記述する必要があります。

Reactにおける副作用とは、主に下記のような処理です。

  • DOM操作
  • API通信
  • console.log
  • ファイルへの書き込み
  • state/propsの変更
  • オブジェクトまたはその内部のプロパティへの代入
  • 配列のpush()

参考:Reactにおける「副作用」とは?

構文

第一引数に実行したい処理としてコールバック関数、第二引数に[]で囲ったstateを渡します。
この第二引数は依存配列と呼ばれ、依存配列に含めたstateの値に更新があった際に、第一引数のコールバック関数が実行されます。

 useEffect(() => {
    処理
  }, [state]); 

シンプルなコード、使用例

主な使い方は下記3パターンです。

  • レンダリングされたタイミングで処理を実行
  • 初回レンダリング時のみ処理を実行
  • 特定のstate(依存配列)の更新があった際に処理を実行

レンダリングされたタイミングで処理を実行

第二引数を記述しない場合、更新用関数が実行された場合など、レンダリングされるタイミングで処理が実行されます。
要するにuseEffectの外側で関数を実行している場合と同じような挙動になります。
下記コンポーネントに二つのconsoleがありますが、同じタイミングで実行されます。

import { useState, useEffect } from "react";
const Sample = () => {
console.log("useEffectの外側だよ");
  useEffect(() => {
    console.log("レンダリングがあったよ");
  }); 
  return (
    ...
  );
};

export default Sample;

初回レンダリングのみ処理を実行

依存配列を空で渡すと初回レンダリング時のみ処理が実行されます。
コード例として、タイマーのようなコンポーネントを作成します。
下記コンポーネントを実行してみると1秒ごとにカウントアップされるコンポーネントになります。
useEffectのコールバック関数でconsoleを記述していますが、依存配列を空で渡しているので初回レンダリング時のみ処理が実行され、consoleは一度しかされません。

import { useState, useEffect } from "react";
const Sample = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    console.log("useEffectが実行されたよ");
    window.setInterval(() => {
      setTime((prev) => prev + 1);
    }, 1000);
  }, []); 

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

export default Sample;

スクリーンショット 2023-01-24 22.13.52.png

window.setInterval()は第二引数に記述したm秒の遅延間隔でコールバック関数が実行されるメソッドです。

特定のstate(依存配列)の更新があった際に処理を実行

先ほどのコードに追加します。


import { useState, useEffect } from "react";
const Sample = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    console.log("useEffectが実行されたよ");
    window.setInterval(() => {
      setTime((prev) => prev + 1);
    }, 1000);
  }, []); 

  // 📌 下記追記
  useEffect(() => {
    console.log("timeの値が変わったよ");
  }, [time]);

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

export default Sample;

すると、依存配列に含めたstate、timeに更新があるたびに処理が実行されていることがわかります。

スクリーンショット 2023-01-24 22.36.21.png

無限ループになってしまうので依存配列のstate値をuseEffectのコールバックの中で書き換えてはいけません。

useEffectのクリーンアップ処理の使い方

useEffectのコールバック関数の戻り値に対してreturnで関数を渡すことができます。
ここでreturnされた関数をクリーンアップ関数と言います。
クリーンアップ関数を渡した場合、依存配列に更新があった際や、そのコンポーネントが削除された際に、クリーンアップ関数が実行されます。

  • 依存配列の値更新時とコンポーネント削除(アンマウント)時
    クリーンアップ関数 → useEffectのコールバック関数 の順番で実行

  • 依存配列を空で渡した場合
    コンポーネント削除(アンマウント)時にクリーンアップ関数が実行

import { useEffect, useState } from "react";

const Example = () => {
  const [isDisp, setIsDisp] = useState(true);
  return (
    <>
      {isDisp && <Timer />}
      <button
        onClick={() => {
          setIsDisp((prev) => !prev);
        }}
      >
        クリック
      </button>
    </>
  );
};

const Timer = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    console.log("Timerコンポーネントが実行されたよ");
    window.setInterval(() => {
      setTime((prev) => prev + 1);
    }, 1000);

    // 📌 下記がクリーンアップ関数
    return () => {
      console.log("Timerコンポーネントが削除されたよ");
    };
  }, []);

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

export default Example;

こちらのコンポーネントを実行してみると下のようなタイマーとボタンが表示され、useEffectの初回マイント時に、consoleが出力されているかと思います。

スクリーンショット 2023-01-25 20.00.29.png
スクリーンショット 2023-01-25 20.01.04.png

クリックボタンを押下してTimerコンポーネントを削除(アンマウント)します。
するとクリーンアップ関数が実行されconsole出力されることがわかると思います。
スクリーンショット 2023-01-25 20.03.20.png

上記サンプルコードではsetInterval()の処理を行なっていますが、Timerコンポーネントをアンマウントした際には、不要になる処理です。
このような時にuseEffectで実行した処理の何らかの後始末を行うためにクリーンアップ関数を使用します。
クリーンアップ関数を使用しなかった場合、コンポーネントをアンマウントしても裏ではsetInterval()処理が行われ続けメモリリークに繋がります。
下記のようにuseEffectの処理を書き換えて、ボタン押下でTimerコンポーネントの非表示を行なってください。処理が行われ続けることがわかるかと思います。

  useEffect(() => {
    console.log("Timerコンポーネントが実行されたよ");
    window.setInterval(() => {
      console.log("setInterval処理"); // 📌 追記
      setTime((prev) => prev + 1);
    }, 1000);

    // 📌 クリーンアップ関数コメントアウト
    // return () => {
    //   console.log("Timerコンポーネントが削除されたよ");
    // };
  }, []);

スクリーンショット 2023-01-25 20.17.15.png

useLayoutEffectとは?

Reactのフックの一つとしてuseEffectと表示に似た名前のuseLayoutEffectというフックがあります。
構文や依存配列、クリーンアップ関数などの使い方はuseEffectと同じです。
これまで記述したサンプルコードのuseEffectuseLayoutEffectに書き換えても同じように動作します。
useEffectとの違いは、実行されるタイミングです。
useEffectは一度画面が描写された後にuseEffectのコールバック関数が実行されるのに対し、useLayoutEffect画面が描写される前に useLayoutEffectのコールバック関数が実行されます。

useLayoutEffectは、useEffectより実行されるタイミングが早い ✍🏻

カスタムフックとは?

useStateなどのReactのHookを内部で使用した関数のことを指します。
React Hookを関数に切り出すことで再利用ができるようになります。
React Hookは基本的に関数コンポーネントのトップレベルでしか呼べないのですが、
use〇〇のようにuseから始まる関数名の中では例外的にReact Hookを呼ぶ事ができます。
そしてカスタムフックであることを明示するために、カスタムフックの命名はuse〇〇にする必要があります。

シンプルな使用例

まずは下記コンポーネントを作成します。

Example.js
import { useState } from "react";
const Example = () => {
  const [count, setCount] = useState(0);
  return (
    <>
      <div>カウント: {count}</div>
      <button
        onClick={() => {
          setCount((prev) => prev + 1);
        }}
      >
        クリック
      </button>
    </>
  );
};

export default Example;

ボタンクリックごとにカウントアップされるコンポーネントです。
スクリーンショット 2023-01-27 13.51.37.png

こちらをカスタムフックを利用してReact Hook部分を切り分けてみたいと思います。
新しくHooks.jsを作成し、そこにReact HooksのuseState部分を切り分けます。
単純にuseState部分を切り分けたのに加え、returnを追加する必要があることに注意してください。
Example.jsで使用するstateと更新用関数をreturnします。
Example.jsではカスタムフックuseHooksを実行し、返ってきたcountsetCountを分割代入して、使用するという流れです。

Hooks.js
import { useState } from "react";
const useHooks = () => {
  const [count, setCount] = useState(0);
  // 📌 Example.jsで使用するstateと更新用関数をreturn
  return {
    count,
    setCount,
  };
};

export default useHooks;
Example.js
import useHooks from "./Hooks";
const Example = () => {
  // 📌 useHooksでreturnされている値を分割代入
  const { count, setCount } = useHooks();
  return (
    <>
      <div>カウント: {count}</div>
      <button
        onClick={() => {
          setCount((prev) => prev + 1);
        }}
      >
        クリック
      </button>
    </>
  );
};

export default Example;

このようにカスタムフックを用いることで、React Hooksを用いた処理を再利用できることがわかるかと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?