【React】addEventListenerを追加した後にremoveEventListener(削除)する方法

<h2>はじめに</h2>
<div>Reactでイベントリスナの追加と削除について書いていきます。</div>
<ul>
<li>イベントリスナの追加はできたけど、削除はできない。</li>
<li>ページ遷移時のイベントリスナの削除の仕方がわからない。</li>
</ul>
<div>上記の方の参考になればと思います。</div>

<h3>addEventListenerについて</h3>

<p>

 `addEventListener()`メソッドは、イベントリスナを登録するの使用されます。
Reactでの使い所としましては、
・ブラウザバックの制御
・PCの再リロード制御
・ブラウザを閉じる時の制御
・APIによってはイベントリスナでしか対応してないもの
上記かなっと思います。(まだまだありそうですが...)



ハンドラを追加する構文は次のようになります。
```javascript:addEventListener.js
element.addEventListener(event, handler[, phase]);
```

まず最初の`element`部分は`EventTarget`と言われ、イベントを設定する所を
指定しています。よく指定されるのが、
・Window
・document
・element( DOMにidとかつけてdocument.getElementById("hoge")とかで取得してくるものとか )
が多いですね。他にもありますので気になる方は次のドキュメントで確認ください。
参考：[EventTargetリファレンスサイト](https://developer.mozilla.org/ja/docs/Web/API/EventTarget)

次に引数部分ですが
`event`
イベント名です。  `click` `keydown` ...etc
参考：[JavaScriptのイベントハンドラ一覧](https://phpjavascriptroom.com/?t=js&p=event)
`handler`
ハンドラ関数.
`phase`
オプションの引数で、ハンドラが動作する “フェーズ” です。基本は使いません。ただ処理速度とか考えるなら知ってるほうがいいです。(以下参考記事)
参考：[addEventListener の第3引数が拡張されてるという話](https://qiita.com/kozy4324/items/85831e2c990d92b8397b)

これらを使用してイベントリスナを登録していきます。

:::note info
イベントリスナ
addEventListener メソッドを使用してイベントに対して登録した関数のこと
:::

:::note info
ハンドラ
イベント発生時に実行する関数のこと
:::

次に登録後、不要になったので削除する場合ですね。

<h3>removeEventListener</h3>

削除するには`removeEventListener()`メソッドを使います。
構文は次になります。
```javascript:removeEventListener.js
// addEventListener とまったく同じ引数にする必要があります。
element.removeEventListener(event, handler[, phase]);
```

削除する時は`addEventListener`とまったく同じ引数にしないと登録したイベントリスナは削除されません。

例：削除されないパターン
```javascript:eventListener.js
// 追加
document.addEventListener( "click" , () => alert('hoge'));
// 削除
document.removeEventListener( "click", () => alert('hoge'));
```
イベントリスナハンドラは削除されません。removeEventListener は別の関数を取得しているためです。同じコードでも関係ありません。

例：削除されるパターン
```javascript:eventListener.js
//　ハンドラ
const handler = () => {
  alert( 'hoge' );
}
// 作成
document.addEventListener("click", handler);
// 削除
document.removeEventListener("click", handler);
```

変数に関数を所持することで削除が可能になります。逆に言いますと変数で関数を所持しないと削除はできなくなります。

参考：[現代のJavaScript : addEventListener](https://ja.javascript.info/introduction-browser-events#ref-1017)

ドキュメントや他webサイトに上記の内容が書いてあるんですけど、ただReactで素直に設定しようとすると落とし穴にはまってしまうんですね。(自分ははまりました(笑))
</p>

<h2>Reactでのイベントリスナの設定</h2>
<h3>素直にイベントリスナを設定した場合(失敗例)</h3>
<p> 
イベントリスナ設定のサンプルコードを作っていきます。
今回はドキュメント押下したらカウントアップするハンドラを使用します。

```react:app.jsx
import { useState } from 'react';

export default function App(props) {
  // イベントリスナ追加されたか確認用
  const [count, setCount] = useState(0);

 // カウントをアップする(ハンドラ)
  const countUp = ()=>{
    setCount(count => {
      count++;
      return count;
    })
  };

  // ボタン押下時の処理
  const SetEvent = (bMode) => {
    // true:イベントリスナー追加 false:イベントリスナー削除
    if (bMode) {
      document.addEventListener('click', countUp);
    } else {
      document.removeEventListener('click', countUp);
    }
  };

  return (
    <div>
      <button
        onClick={() => {
          SetEvent(true);
        }}
      >
        addEventListener
      </button>
      <button
        onClick={() => {
          SetEvent(false);
        }}
      >
        removeEventListener
      </button>
      <div>
        <h1>count:{count}</h1>
      </div>
    </div>
  );
}
```

画面がこちら
![addevent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1837039/827c9504-a7b8-0af9-9b44-5123c040f0f7.png)

`addEventListener` ボタンを押下したらイベントリスナが登録され、`removeEventListener`で削除できるように作ったつもりです。
削除する時に気をつける、引数一緒も意識しました。(削除はされません)

実際の動き(画像で申し訳ないです。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1837039/fd632c81-0d64-f331-3faf-b1163321ed52.png)


`addEventListener`のボタンを押下する度にイベントリスナが追加されています。
`removeEventListener`のボタンでイベントリスナが削除されていません。
引数は同じなのに削除できないのが不思議です。

<h3>Reactの性質について考えて作り直したもの(成功例)</h3>


Reactは状態が変化すると再レンダリングします。レンダリング処理が走る度にコンポーネント内の関数が新規で作成されているのです。
という事は同じ変数名の関数でも厳密に見ると違う関数になるという事です。
レンダリングの度に違う関数....、state使ってるから外にも書けないし....。
となりましたが、reactに優秀なhooksがいます。関数をメモ化すれば新規で作られる事はありません。
<h3>useCallback</h3>
今回はこのhooksを使用して関数をメモ化していきます。

参考：[React hooksを基礎から理解する (useCallback編+ React.memo)](https://qiita.com/seira/items/8a170cc950241a8fdb23)

作り直したサンプルコード
```react:app.jsx
import { useState, useCallback } from 'react';

export default function App(props) {
  // イベントリスナ追加されたか確認用
  const [count, setCount] = useState(0);

 // カウントをアップする(ハンドラ)
  const countUp = useCallback(()=>{
    setCount(count => {
      count++;
      return count;
    })
  },[]);

  // ボタン押下時の処理
  const SetEvent = (bMode) => {
    // true:イベントリスナー追加 false:イベントリスナー削除
    if (bMode) {
      document.addEventListener('click', countUp);
    } else {
      document.removeEventListener('click', countUp);
    }
  };

  return (
    <div>
      <button
        onClick={() => {
          SetEvent(true);
        }}
      >
        addEventListener
      </button>
      <button
        onClick={() => {
          SetEvent(false);
        }}
      >
        removeEventListener
      </button>
      <div>
        <h1>count:{count}</h1>
      </div>
    </div>
  );
}
```

イベントリスナに登録する関数(カウントアップ関数)を `useCallback `でラップしました。これで関数がメモ化されたのでイベントリスナの積み重ねがなくなり、削除もできるようになりました。

今回は説明はしませんが、メモ化されたハンドラないでif文を使い、分岐の判定を `useState`でしたい場合は、かわりに`useRef`を使うとしっかりと分岐の判定をしてくれます。

:::note info
ポイント
Reactでイベントリスナーを設定する場合は関数(ハンドラ)をメモ化しましょう。
:::
</p>

<h2>Reactでページ遷移するタイミグで解除する方法</h2>
<p>

遷移する時は`useEffect`を使用すると遷移するタイミングで解除できます。


```react:app.jsx  

 // カウントをアップする(ハンドラ)
  const countUp = useCallback(()=>{
    setCount(count => {
      count++;
      return count;
    })
  },[]);

  // イベントリスナーの設定
  useEffect(() => {
    // イベントの設定
    document.addEventListener('click', countUp);
   
    return () => {
      // イベントの設定解除
      document.removeEventListener('click', countUp);
    };
  }, []);
```
上のコードの例ですとレンダリングされた時にイベントリスナを設定して、ページ遷移するタイミングでイベントリスナを削除します。
こんな形で遷移時に追加したいイベントを書きます。
</p>
<h2>終わり</h2>
<p>
説明不足もあると思いますが、イベントリスナの設定を自分なりにまとめられてよかったです。


</p>