[React] Effect（副作用）について理解する！

## Effectとは？
React Hooksの一種で副作用を実行するための処理
- APIからのデータ取得
- DOMの更新

## 構文
副作用は `useEffect()`によって定義される。

### 第一引数
副作用を実行するコールバック関数（引数なし）を指定する。

```tsx
useEffect(() => {
  console.log('レンダリングごとに実行');
});
```

### クリーンアップ関数
第一引数の関数はクリーンアップ関数を返すことが可能。
クリーンアップ関数は **「コンポーネントのアンマウント時」 「コンポーネントの再レンダリング時」に実行** される



```tsx
useEffect(() => {
  console.log('レンダリングごとに実行');

  return () => {
    console.log('アンマウント時に実行');
  };
});
```

### 第二引数
第二引数には **「依存配列」** を指定することができる。

#### 依存配列
副作用関数が依存する値を配列形式で指定する。
**「配列内の値が変更されるたびに、指定した副作用が再実行」** される。

---

#### 依存配列を省略した場合
依存配列を省略すると、第一引数の関数は **「コンポーネントがレンダリングされるたびに実行」** される。

```tsx
useEffect(() => {
  console.log('レンダリングごとに実行');
});
```

#### 依存配列に`[]` を指定した場合
依存配列に空の配列 `[]` を指定すると、副作用は **「コンポーネントのマウント時に一度だけ実行」** され、アンマウント時にクリーンアップ関数が実行されます。

```tsx
useEffect(() => {
  console.log('マウント時に一度だけ実行');

  return () => {
    console.log('アンマウント時に実行');
  };
}, []);
```

#### 依存配列に値を指定した場合
`userId` の値が変わるたびに `fetchUserProfile` 関数が呼ばれ、新しいユーザープロファイルを取得する
```tsx
useEffect(() => {
  fetchUserProfile(userId).then(data => {
    setUserProfile(data);
  });
}, [userId]);
```

## 依存配列のWarning

以下のコードは`timeLeft`と`maxCount`を副作用の中で使用しているが、これらが依存配列に含まれていないために`warning`が発生する。

```tsx
useEffect(() => { 
  if (timeLeft === 0) setCount(maxCount); 
});
```

### 発生理由
この副作用は `timeLeft`の値を確認、 → `maxCount`を利用して値を更新しているが、
`timeLeft` 及び、 `maxCount`の値に変更がない場合、 **副作用の実行に意味がないため**

### 正しい実装
```tsx
useEffect(() => { 
  if (timeLeft === 0) setTimeLeft(maxCount); 
}, [timeLeft, maxCount]);
```
このようにすることで関連する副作用が適切に実行される。

## useEffectは複数定義可能
`useEffect` はコンポーネント内で複数回定義することが可能。
これにより、異なる副作用を分けて管理できる。

```tsx
useEffect(() => {
  console.log('レンダリングごとに実行');
}, []);

useEffect(() => {
  console.log('レンダリング');
});
```

## サンプルコード
ここまでの説明を踏まえたタイマーコンポーネントの実装です。
```tsx
import { FC, useEffect, useState } from 'react';

type TimerProps = {
  initialCount?: number;
};

const DEFAULT_COUNT = 60;

const Timer: FC<TimerProps> = ({ initialCount = DEFAULT_COUNT }) => {
  const [secondsLeft, setSecondsLeft] = useState(initialCount);
  const tick = (): void => setSecondsLeft((current) => current - 1);
  const resetTimer = (): void => setSecondsLeft(initialCount);

  useEffect(() => {
    if (secondsLeft === 0) setSecondsLeft(initialCount);
  }, [secondsLeft, initialCount]);

  useEffect(() => {
    const timerID = setInterval(() => {
      tick();
    }, 1000);
    return () => clearInterval(timerID);
  }, []);

  return (
    <div>
      <div>
        <div>Count</div>
        <div>{secondsLeft}</div>
      </div>
      <button onClick={resetTimer}>
        Reset
      </button>
    </div>
  );
};

export default Timer;
```

## :memo: [参考] クラスコンポーネント
昔のクラスベースのReactコンポーネントで記載された場合

```tsx
import React, { Component } from 'react';

type TimerProps = {
  initialCount?: number;
};

type TimerState = {
  secondsLeft: number;
};

const DEFAULT_COUNT = 60;

class Timer extends Component<TimerProps, TimerState> {
  timerID?: NodeJS.Timeout;

  static defaultProps = {
    initialCount: DEFAULT_COUNT,
  };

  constructor(props: TimerProps) {
    super(props);
    this.state = {
      secondsLeft: props.initialCount || DEFAULT_COUNT
    };
  }

  componentDidMount() {
    this.startTimer();
  }

  componentDidUpdate(prevProps: TimerProps, prevState: TimerState) {
    if (this.state.secondsLeft === 0) {
      this.resetTimer();
    }
  }

  componentWillUnmount() {
    this.stopTimer();
  }

  startTimer = () => {
    this.timerID = setInterval(() => {
      this.tick();
    }, 1000);
  }

  stopTimer = () => {
    if (this.timerID) {
      clearInterval(this.timerID);
    }
  }

  tick = () => {
    this.setState((prevState) => ({
      secondsLeft: prevState.secondsLeft - 1
    }));
  }

  resetTimer = () => {
    this.setState({
      secondsLeft: this.props.initialCount || DEFAULT_COUNT
    });
  }

  render() {
    return (
      <div>
        <div>
          <div>Count</div>
          <div>{this.state.secondsLeft}</div>
        </div>
        <button onClick={this.resetTimer}>
          Reset
        </button>
      </div>
    );
  }
}

export default Timer;
```

### まとめ
副作用をコンポーネントから分離して定義できるので読みやすいですね。