【React】useEffectの基本的な使い方・活用術・注意点

おはこんばんちは、[@ちーず](https://qiita.com/cheez921)です。
アドベントカレンダー3日目にして、すでに毎日記事を書くことの大変さを実感してます :qiitan-cry:

本日のテーマは`useEffect`です！

## `useEffect`とは
`useEffect`とは、関数コンポーネントで副作用を実行するためのhookです。
...と言われてもReactにおける副作用ってなんぞ？ってなりますよね。

Reactのコンポーネントの世界において、副作用はコンポーネントのライフサイクルのことを指します。(多分)

▼ コンポーネントのライフサイクル 
　 (クラスコンポーネントにおけるライフサイクルメソッド名を添えて)

* マウントされる (`componentDidMount`)
* 更新される (`componentDidUpdate`)
* アンマウントされる (`componentWillUnmount`)


### 書き方

```jsx
// 第一引数: コールバック関数(任意でcleanup関数を返す)
// 第二引数: 配列

useEffect(() => {
  // 実行したい処理
  return () => {
    // cleanupの処理
  }
}, [input])

```

#### 毎回のレンダリング後に実行
第二引数を空にすることで、レンダリング毎に実行されます。

```jsx
useEffect(() => {
  // 処理
})
```

:::note alert
コンポーネントはstateやpropsなどに変更がある度にレンダリングされてしまいます。
そのため予期しないケースでuseEffectが実行されてしまう可能性があるため、第2引数を空にすることは危険です。
:::

#### 初回のレンダリング後に実行
第二引数をの配列の中身を空にすると、
初回のレンダリング後のみ実行されます。

```jsx
useEffect(() => {
  // 処理
}, [])
```

#### 指定した値に変化があった時に実行
第二引数の配列に値を設定することで、
その値に変更がある度に実行することができます。

```jsx
useEffect(() => {
  // 処理
}, [value]))
```

:::note info
第二引数は、基本的にはlint(react-hooks/exhaustive-deps)とエディタの機能に力を借りて
自動で設定すればokです！
:::


## `useEffect`の活用術

### ステートの変更を監視して実行

いっちばん基本的な使われ方ですが、
ステートの変更を監視して処理を実行することができます。

▼ 例

```js
useEffect(() => {
  if (count < 10) return;
  console.log('countがもう10以上やで')
}, [count]);
```

条件に当てはまらない、実行したくない場合は早期リターンしてあげることをおすすめします。

### 外側から実行条件を渡して汎用化

処理は同様だけど、処理を実行させる条件が異なるケースがあると思います。
そのような場合は、`useEffect`を別コンポーネントもしくはカスタムフックとして別途作成し、
条件を`props`や`引数`で渡せるようにします。

▼ よく使う`props`や`引数

* `skip`: 処理をスキップするか - falseの時のみ実行させる
* `prepared`: 準備ができたか - trueの時のみ実行させる
* `onMounted / onUnMount`: マウント または アンマウント で実行させる


▼ 例: カスタムフックを使用した例

```js
export const useCustomHook = (skip: false) => {
  useEffect(() => {
    if (skip) return;
    // 共通化したい処理
  }, [skip])
} 
```

カスタムフックとはなんぞ？と思った人にはおすすめの記事があります。(宣伝)

https://qiita.com/cheez921/items/af5878b0c6db376dbaf0


※ 活用術に関しては、発見次第今後追記していく予定です！

## `useEffect`の注意事項
一見便利な`useEffect`ですが、使い方を誤ると予期せぬバグを産んでしまいがちです。
そのため、どのようなバグが発生しがちか、どのように対処すべきかも理解した上で使えるとより良いです。

### 無限ループに気をつける
`useEffect`は特に無限ループに陥りがちです。

ありがちなのが、ステートの更新を`useEffect`の中で行っているが、
その`useEffect`はステートが更新されたら再度実行されるようになっていて、
更新 → 実行 をひたすら繰り返してしまうことです。

```ts
const [count, setCount] = useState(0)
		 
useEffect(() => {
  setCounter(prev => prev + 1)
}, [count])
```

`useEffect`は、ちゃんと無限ループがどのようなケースで発生しうるかを理解して使うことが大事です。

▼ 参考

https://qiita.com/takano-h/items/11f7b35dbf4844f7565d

### メモリリーク対策をしっかりする

メモリリークとは、実行中のプログラムが割当てたメモリ領域を解放し忘れることで起きるバグです。

`useEffect`を使った場合、非同期の処理においてResponse返ってくるよりも先にコンポーネントが消失した場合に発生しうります。

その場合は、ちゃんとコンポーネントがマウントされた状態であることを判定した上で実行しましょう。

▼ 例

```jsx
useEffect(() => {
  let isMounted = true;

  // promise() - Promiseを返す何かしらの処理
  promise().then((n) => {
    isMounted && setState(n);
  });

  // アンマウント時にmountedをfalseに変更
  return () => {
    isMounted = false;
  };
}
```



▼ 参考

https://zenn.dev/maktub_bros/articles/3366d053275d60

### イベントを追加した時は、クリーンアップ時に削除する

`addEventListener`を`useEffect`内で使う場合、
`useEffect`が走る度にイベントが追加され続けてしまいます。
そのため、クリーンアップ時にイベントを一度削除するよう制御しましょう。

▼ 例

```jsx
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, [handleResize]);
```

---

以上、「`useEffect`の基本的な使い方と活用術」でした！
明日は`useRef`の基本的な使い方と活用術に関してです！
お楽しみに:hibiscus: