【useEffect】クリーンアップ処理がなぜ必要なのか体感したい方へ(具体的なコードを交えて説明)

クリーンアップ処理が必要な理由を端的に言うと、「無駄な処理は実行すべきでないから」となります。が、自分は実際どのような無駄があるのか体感するまで重要性を感じられずにいたので記事にしました。

## 本記事における言葉の意味と実体(フロント界隈での意味)
**・クリーンアップ処理**
不要な処理の実行をさせない処理。不要な処理のお掃除。

**・アンマウント**
DOMから削除されること。例えば/homeのページから/aboutのページにユーザーが遷移した際、/homeを構成するコンポーネントはDOMから削除され、これをアンマウントという。

https://ja.legacy.reactjs.org/docs/react-component.html

**・useEffect内のクリーンナップ処理**

```???.tsx
useEffect(()=>{
    //↓これ！
    return () => ~
},[])
```
useEffectで戻り値に関数を定義するとクリーンアップ処理として動作する。実行されるタイミングは**コンポーネントがアンマウントされる直前。**

**・メモリリーク**
利用できるメモリ量が減ること。レンダリングパフォーマンスが落ち、ユーザー体験を落とす要因になる。クリーンアップ処理を行わないと引き起こされることがある。

本題へ行きます。（本記事のソースコードを実際に試す方はページ遷移できる環境をreact routerやnext.js等で用意する必要があります）
## クリーンアップ処理の重要性が体感できる　例１:タイマー


```Timer.ts
import { useState, useEffect } from "react";

const Timer = () => {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const intervalId = setInterval(() => {
            //動作確認用。クリーンナップ関数がない場合、ページ移動後（アンマウント時）も実行されることが確認できます。
            console.log("id", intervalId);
            setCount(prevCount => prevCount + 1);
        }, 1000);

        // クリーンアップ関数でsetIntervalをクリア！
        return () => clearInterval(intervalId);
    }, []);

    return <div>Count: {count}</div>;
};

export default TimerComponent;

```

上記は毎秒カウントを行う関数です。

クリーンナップ関数がない場合、上記を利用しているコンポーネントがアンマウントされてもsetIntervalの処理が止まってくれません。
↓他のページに移ったが、処理が続いている😭
![Videotogif (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486141/ba0d2b88-9459-5293-6a0e-d91cbeb0a5bb.gif)



これはsetIntervalの処理を**Reactが直接管理していない**ことに起因します。DOMから消えたんだから処理も止まるでしょ！と思いますが、setIntervalの処理はReactの関心外のため、クリーンナップ関数を定義する必要があります。（逆に言うと適切なクリーンナップ処理を施せばsetIntervalの利用はOKということ）

clearIntervalメソッドを使うとsetIntervalメソッドによって設定された繰り返し実行されるタイマーを停止することができます。

https://developer.mozilla.org/ja/docs/Web/API/clearInterval

## クリーンアップ処理の重要性が体感できる　例2:イベントリスナー

```Scroll.tsx
"use client";
import Link from "next/link";
import React, { useEffect } from "react";

const Scroll = () => {
    useEffect(() => {
        const handleScroll = () => {
            console.log("ユーザーがスクロールしている！！！");
        };
        window.addEventListener("scroll", handleScroll);

        //クリーンナップでイベントリスナーを削除！
        return () => {
            window.removeEventListener("scroll", handleScroll);
        };
    }, []);

    return (
        <>
            <div className="h-screen">
                コンソールを見てみてね
                <Link href="/test">test</Link>
            </div>
            <div className="h-screen">コンソールを見てみてね</div>
        </>
    );
};

export default Scroll;

```
（↑tailwind cssを使っています）
上記はユーザーがスクロールするたびにコンソールで「ユーザーがスクロールしている！！！」と表示するような処理です。
クリーンナップが無い場合、ページ遷移（アンマウント後）しても、windowオブジェクトに紐づいたscrollのイベントリスナーが消えず、下記のようになってしまいます。
これはsetInterval同様、イベントリスナーの管理をReactが行なっていないことに起因しています。

↓他のページに移ったが、スクロールするたび処理が続いている😭

![Videotogif (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486141/aab47e6a-3037-3632-56bb-104f80671af7.gif)

イベントリスナーを利用した際はアンマウント時にリスナーの削除をしているか確認する癖をつけておくといいかもです。

また、イベントリスナーはremoveEventListenerメソッドで削除することができます。

https://developer.mozilla.org/ja/docs/Web/API/EventTarget/removeEventListener

## 終わり

他にもwebsocket利用時やapi呼び出しの際にクリーンアップを適切に施さないとメモリリークにつながってしまうことがあるようです。

useEffect利用時はReactのルールを守って開発できているか確認すると良いと思いました👍