useEffect 備忘録

## はじめに
useEffectについて、以下の記事がとても分かりやすかったので、記事を読んでインプットしたものをまとめました。

https://overreacted.io/ja/a-complete-guide-to-useeffect/

他参考

https://react.dev/reference/react/useEffect

https://qiita.com/keiya01/items/fc5c725fed1ec53c24c5


# useEffectの前に・・・
レンダリングについておさらいします。

## それぞれの render は独自の props と state を保持している



```jsx
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
```
このコードにおける**count**はただの数字です。 

コンポーネントが一番初めに render する際、useState() から出力される count 変数は 0 です。setCount(1)を呼ぶと、React はコンポーネントは再度呼び出します。その際、 count は 1 となります。

```jsx
// 初期 render 時
function Counter() {
  const count = 0; // useState()の戻り値
  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// クリック後、関数が呼び出される
function Counter() {
  const count = 1; // useState()の戻り値
  // ...
  <p>You clicked {count} times</p>;
  // ...
}

// もう一度クリックすると、再度呼ばれる
function Counter() {
  const count = 2; // useState()の戻り値
  // ...
  <p>You clicked {count} times</p>;
  // ...
}
```

**state をアップデートする度、React はコンポーネント関数を呼び出します。**
それぞれの render 結果は定数として定義された **counter state** を見ることができます。

`<p>You clicked {count} times</p>`

何か特別なデータバインディングをしている訳ではなく、{count}には、結果にReactが提供した数字を組み込んでいます。
setCountするとcountの値を使ってコンポーネントが呼び出されます。
そして、render結果にマッチするようにDOMをアップデートしています。

count定数は、特定のrenderで**時間の経過とともに変化するものではない**です。
コンポーネント関数が呼び出されることによって、countの値を見ることができるからです。
そして各 render は**それぞれそのrenderに隔離されたcount値**を見ることができます。

### 結果からわかること

- count は定数であり呼び出される関数ごとに保持している

## それぞれの render は独自のイベントハンドラを保持している

イベントハンドラの場合はどうなるのか見てみます。

以下のコードは、3 秒後に count の値を alert するイベントです。。
```jsx
function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert("You clicked on: " + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>アラートを表示</button>
    </div>
  );
}
```
![スクリーンショット 2023-09-10 11.55.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/29d5e189-ca58-1e73-5d33-8812f10cff24.png)

### 下記の手順を実行したとき、アラートに表示される数字は何になるのか

countを3回まで増やす
↓
「アラート表示」を押下
↓
タイムアウトが発火する前に count を 5 まで増やす

### 結果

アラートは押下時の state をキャプチャーします。

![ezgif.com-video-to-gif (10).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/b59a8631-8ca0-e2d4-40c4-16e7d47b3c84.gif)

### 結果からわかること

- 関数は何度も呼ばれる（render 毎に一度）が、その都度 count値は定数であり何かの値でセットされている（その render の state）
- 特定の render の中では**props と state は一生変わらない。** 
- props と state が特定の render に隔離されていて同じ
- それを使用してる値（イベントハンドラも含む）も同様に**特定の render に属している**

## それぞれの render は独自のエフェクトを保持している

DOMに変更が走ってブラウザに描画された後にエフェクトは実行されます。

概念的には一つのエフェクト（ドキュメントのタイトルを変える）ですが、render されるごとに **別の関数** として表されています。
そしてそれぞれのエフェクト関数は**特定の render に属する props と state**を参照することができます。

>**概念的に、エフェクトは render 結果 の一部であると想像してもらってもいいです。**

### それぞれの render は全てを保持している

エフェクトは render の後に実行され、概念的にはコンポーネント出力の一部であり、特定の render 内の state と props を参照できます。

```jsx
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      console.log(`You clicked ${count} times`);
    }, 3000);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
```
![スクリーンショット 2023-09-10 13.18.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/2a8050c2-cd59-fd25-9458-bcadbf9e33fc.png)


### 結果

特定の render 内に属する count の値が順次出力されます。

![ezgif.com-video-to-gif (11).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/fa2129ce-5a18-16bc-d1fc-d7cb22529069.gif)

**※class コンポーネントの this.state は、このような挙動をしません。**

```jsx
import React, { Component } from "react";

class Example extends Component {
  state = {
    count: 0,
  };
  componentDidMount() {
    setTimeout(() => {
      console.log(`You clicked ${this.state.count} times`);
    }, 3000);
  }
  componentDidUpdate() {
    setTimeout(() => {
      console.log(`You clicked ${this.state.count} times`);
    }, 3000);
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button
          onClick={() =>
            this.setState({
              count: this.state.count + 1,
            })
          }
        >
          Click me
        </button>
      </div>
    );
  }
}
```
![ezgif.com-video-to-gif (12).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/62ede43d-b3c5-9eac-5925-c33ccba03518.gif)
this.state.count は**特定の render に属する値ではなく、常に最新の count の値を参照します**。なので、代わりに 5 が順番に表示されます。

### 結果からわかること

- コンポーネント render 内の全ての関数（コンポーネント内で定義されてるイベントハンドラ、エフェクト、タイムアウト、APIの呼び出しなどを含む）は定義されてる特定の render 内の props と state をキャプチャーする

そのため、以下の２つは同じ挙動をします

```jsx
function Example(props) {
  useEffect(() => {
    setTimeout(() => {
      console.log(props.counter);
    }, 1000);
  });
  // ...
}

function Example(props) {
  const counter = props.counter;
  useEffect(() => {
    setTimeout(() => {
      console.log(counter);
    }, 1000);
  });
  // ...
}
```

重要なことは、**props か state を「早めに」コンポーネント内で呼ぼうが呼ばまいが関係ないということです**。
一つの render 内のスコープでは、 props と state は変わりません。

特定の render 内の値ではなく最新の値をエフェクト内で定義されてる callback内で 使いたい 場合は、**useRef**を利用するのが一番簡単な方法です。

### useRefを使い、最新の値を参照する

```jsx
function Example() {
  const [count, setCount] = useState(0);
  const latestCount = useRef(count);

  useEffect(() => {
    // mutable な最新の値をセットする
    latestCount.current = count;
    setTimeout(() => {
      // mutable な最新の値を読む
      console.log(`You clicked ${latestCount.current} times`);
    }, 3000);
  });
```

### Cleanupのときはどうなるのか

下記の例

``` jsx
useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
    };
  });
```

初期 render 時には props は {id: 10} として、2回目の render 時は {id: 20} になるとします。
この際の挙動はどうなるのでしょうか。

:::note info
正解例
①React が　{id: 20} の UI を render する。
②ブラウザが描画する。{id: 20} の時の UI が画面に表示される。
③**React が {id: 10} のエフェクトを cleanup する。**
④React が {id: 20} のエフェクトを実行する。
:::

:::note warn
少し間違いです。
①React が {id: 10} のエフェクトを cleanup する。
②React が　{id: 20} の UI を render する。
③React が {id: 20} のエフェクトを実行する。
:::

Reactはブラウザの描画が終わった後に、初めてエフェクトを実行します。
**前のエフェクトは新しい props で re-render されてから cleanup されます。**

なぜ、前のエフェクトの cleanup は props が {id: 20} に変わって re-render された後に実行されてるのに、古い {id: 10} の props が見えてるのでしょうか。

答えは、**エフェクトの cleanup はどういう意味であろうと最新の値を読んだりしないからです。** エフェクトが定義されている特定の render 内の props を読んでいるからです。

## React にエフェクトを比較することを教える

```jsx
function Greeting({ name }) {
  useEffect(() => {
    document.title = 'Hello, ' + name;
  });
  return (
    <h1 className="Greeting">
      Hello, {name}
    </h1>
  );
}
```

nameをアップデートする場合

```jsx
<h1 className="Greeting">
  Hello, Dan
</h1>
```
から

```jsx
<h1 className="Greeting">
  Hello, Yuzhi
</h1>
```
へ

Reactが受け取るオブジェクトは
```
const oldProps = {className: 'Greeting', children: 'Hello, Dan'};
const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'};
```

それぞれの props を比べて、 children は変更しているので DOM アップデートは必要ですが className は変わっていないので、このような処理をします。

```jsx
domNode.innerText = 'Hello, Yuzhi';
// domNode.classNameは触る必要なし
```

エフェクトで以下の場合はどうなるのでしょうか。

```jsx
function Greeting({ name }) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = 'Hello, ' + name;
  });

  return (
    <h1 className="Greeting">
      Hello, {name}
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </h1>
  );
}
```

この場合、countのstateは使用していないが、document.title を name prop でシンクロさせているので、countが変更されるたびに、再レンダリングされてしまいます。

- React は一度関数を呼ばないと、その関数が何をしているか推測することはできない。
- エフェクトは、単純に DOM の違いを勝手に検知できない。

そのため、不必要なエフェクトを再実行したくない場合は、**依存配列**（deps とも言われる）というものを第 2 引数として useEffect に渡します。

```jsx
  useEffect(() => {
    document.title = 'Hello, ' + name;
  }, [name]); // Our deps
```

>「関数の戻り値が分からないのは知ってるけど、render scope 内で name しか使ってないことを約束するよ」と React に言ってるみたいなものです。

もし配列内のそれぞれの値が現在と一つ前のエフェクト実行時と同じであれば、シンクロするものがないので React はエフェクトをスキップします