【React】useEffectの使い方 💡

# useEffectとは

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

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

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

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

参考：[Reactにおける「副作用」とは？](https://qiita.com/Mitsuw0/items/801f783ca74b062c1ed8)


## 構文

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

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

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

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

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

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

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

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

export default Sample;

```

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

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

```jsx
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/b6e972c9-ec43-9bb4-2511-4a0e565331fa.png)

:::note

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

:::

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

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

```jsx

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/044160b0-f901-2831-9c86-3f8e6bc8f9ab.png)

:::note alert

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

:::

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

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

:::note 

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

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

:::

```jsx
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/8613a956-af44-6479-e908-c8e9562458e3.png)
![スクリーンショット 2023-01-25 20.01.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/58052af6-bec8-77ac-d876-fa11fa318f41.png)

クリックボタンを押下して`Timer`コンポーネントを削除（アンマウント）します。
するとクリーンアップ関数が実行され`console`出力されることがわかると思います。
![スクリーンショット 2023-01-25 20.03.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/a83d4bb9-94d6-8b56-b062-0711de4c2258.png)

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

```jsx
  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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/6203924f-b584-4b8f-2841-1efc1de1052b.png)


## useLayoutEffectとは？

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

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

## カスタムフックとは？

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


### シンプルな使用例

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

```jsx: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/c83b7134-8e6f-e8fa-750e-bc4d8207463e.png)

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

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

export default useHooks;
```

```jsx: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を用いた処理を再利用できることがわかるかと思います。