React hooks useEffectについて学ぶ 1

React hooksの1つである、`useEffect` について学んでいきます。

さて `useEffect`とは一体何者なのでしょうか？
## useEffectとは

> 副作用 (effect) フック により、関数コンポーネント内で副作用を実行することができるようになります

参考：[副作用フックの利用法](https://ja.reactjs.org/docs/hooks-effect.html)

なるほど、useEffectとは `副作用`を実行するためのもののようです。
...`副作用`ってなんだろう？

## 副作用とは

> ある機能がプログラム上のデータを変化させ、それ以降の演算の結果に影響を与えることを指します

参考 : [副作用](https://gimo.jp/glossary/details/side_effect.html#:~:text=%E5%89%AF%E4%BD%9C%E7%94%A8%E3%81%A8%E3%81%AF%E3%80%81%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0,%E3%81%97%E3%81%BE%E3%81%86%E3%81%93%E3%81%A8%E3%82%92%E6%8C%87%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)

Reactで言うなら、 `レンダリング後、それ以降の演算の結果に影響を与えることを指します` と言えそうです。
でもそれってどういうことなのでしょう。そんなこと、してもよいのでしょうか？
参考 : [レンダリングとは](https://de-milestones.com/react-mount-rendering/)

## 副作用を試す

`それ以降の演算の結果に影響を与える` ⇦まだよくわからないので試してみましょう。

分かりやすそうな、`DOMの書き換え` を行います。
レンダリング後、h1を書き換えてみます。

```jsx
import { useEffect } from "react"

export const Text = () => {

    useEffect(() => {
        const elem = document.getElementById('title') as HTMLTitleElement; // 絶対h1がある！
        elem.innerText = 'こんばんわ'
    })

    return (
        <div>
            <h1 id="title">こんにちわ</h1>
            <p>使ってみよう</p>
        </div>
    )
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/e5b52759-9806-5a5f-3b93-24142b1fad2a.gif" width="400">

レンダリング時、h1には `こんにちわ` というテキストが入っています。
副作用を用いて、h1のテキストを `こんばんわ` に変化させることができました。

---
続いては実験です、副作用をコメントアウトしてみましょう。
同じようにテキストを書き換えてくれるでしょうか？

```jsx
// useEffect(() => {
     const elem = document.getElementById('title') as HTMLTitleElement; // 絶対h1がある！
     elem.innerText = 'こんばんわ'
// })
```

あれ、ページは真っ白で、エラーが起きてしまいました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/f3f79386-ac3c-f6a4-f819-6e0fa8261bd4.png" width="400">


```html
Cannot set properties of null (setting 'innerText')
// null のプロパティを設定できません(「innerText」を設定)
```

`id="title"` を見つけることができていません。
つまり、`まだレンダリングが終わっていない(DOMのことわかってない)` 状態で `id="title"` を探しに行ってしまいました。

この実験で、
`それ以降の演算の結果に影響を与える` というのは、`レンダリングし終わった後に動き出す`
ということだったのが実感できました。

## useEffectが動くタイミング

`レンダリングし終わった後に動き出す` のはわかりました。
では、それって具体的にいつなのでしょう？

useEffectが動くタイミングが３つあります。

1. 値の変更時
2. 初回レンダリング時 
3. レンダリング後 // 先ほどの実験です

3は実験すみなので、1からみていきます。

## useEffectが動くタイミング[値の変更時]

これはuseStateを使った実験がわかりやすそうです。
2種類のuseStateにて、`count`、`count2` をそれぞれ用意しました。

```jsx
export const Counter = () => {

    const [count, setCount] = useState<number>(0)
    const [count2, setCount2] = useState<number>(0)

    useEffect(() => {
        console.log(count)
    })

    useEffect(() => {
        console.log(count2)
    })


    const incrementCount = () => {
        setCount(c => c + 1) // *1
    }
    const incrementCount2 = () => {
        setCount2(c => c + 1) // *1
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={incrementCount}>1増える</button>
            <button onClick={incrementCount2}>2増える</button>
        </div>
    )
}
```

早速、incrementCount を押してみましょう。 
`*1` についてはこちらをどうぞ [この時のcってなに](https://ja.reactjs.org/docs/hooks-reference.html#functional-updates)

![画面収録_2022-09-30_1_00_32_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/8f659f4d-29c8-a767-9dbc-d00b65bf7cc7.gif)

`1増えるボタン`を押すたびに、console.logが走っているのがわかります！
`値の変更時` に動いているのを確認できました。

あれ、全然関係ない `console.log(count2)` も動いてしまっていますね。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/cc2c56de-d180-a954-73b8-9a189aba7359.png" width="300"/>

この場合、useEffectの第二引数に、`useEffectの動作を依存させる値` を入れることができます。

```jsx
   useEffect(() => {
        console.log(count)
    }, [count]) // countに反応して動く

    useEffect(() => {
        console.log(count2)
    }, [count2]) // count2に反応して動く
```

![画面収録_2022-09-30_1_08_18_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/23f3eef2-cc3d-8fe8-2c1d-1d78c8492d7d.gif)

それぞれ独立して反応させることができました！

## useEffectが動くタイミング[初回レンダリング時]

続いて、2,初回レンダリング時 を見ていきます。
やりかたは簡単、第二引数を空配列を指定するだけです。

```jsx
    useEffect(() => {
        console.log(count)
    }, []) //空っぽ

    useEffect(() => {
        console.log(count2)
    }, [])//空っぽ
```

![画面収録_2022-09-30_1_19_13_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/4b4d8e4f-d4a9-cbf6-5b12-fb2d531ad643.gif)

まず0が4つ出ましたね。([これについてはこちらをどうぞ](https://ja.reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects))

その後何度クリックしても、console.logが出てくれません。
これは、第二引数を空配列[]にしたことで、

`2,初回レンダリング時動く(それ以降動かない)`

ということができるのです。

 参考:[マウント・アンマウント時のみuseEffectを実行する場合](https://nishinatoshiharu.com/useeffect-scond-arg-usage/)

## useEffectの無限ループ

useEffectは無限ループにはまってしまった経験はありませんか？
どのような時に起こるのでしょうか。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/23913945-6ce1-4912-673f-1987aafd620a.gif" width="300" alt="無限にカウントを1足し続けるgif画像">

実装は簡単です。

```jsx
    useEffect(() => {
        setCount(c => c + 1) // 追加
        console.log(`----${count}----`)
    })
```

useEffectの中に `useStateで使用するset部分` を使って関数を走らせただけです。

```html
初回レンダリングでuseEffect走るね、その時のsetCountするね
 -> 値の変更されたからもう一度useEffect走るね、その時のsetCountするね
 -> 値の変更されたからもう一度useEffect走るね、その時のsetCountするね
:
```

この状態を避けるためにも、 [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) を設定しておくのが良いです。
![画面収録_2022-10-03_1_32_27_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/97612093-1b22-0891-5de1-3ca8ed0497ae.gif)

ちなみにこれを修正するには `別のstateなどに依存` するようにします。

```jsx
    useEffect(() => {
        setCount(c => c + 1)
    }, [userId]) // userIdに変更があった時だけ動いてね
```

参考:[なぜ useEffect では無限ループが起こり得るのか](https://numb86-tech.hatenablog.com/entry/2019/10/23/121332)

--- 

いったん今回はここまでとします
次回はもう少しuseEffectのことを深く見ていきましょう、

---

読んでおきたい記事: [useEffect完全ガイド](https://overreacted.io/ja/a-complete-guide-to-useeffect/)