はじめに
- イベントリスナの追加はできたけど、削除はできない。
- ページ遷移時のイベントリスナの削除の仕方がわからない。
addEventListenerについて
addEventListener()
メソッドは、イベントリスナを登録するの使用されます。
Reactでの使い所としましては、
・ブラウザバックの制御
・PCの再リロード制御
・ブラウザを閉じる時の制御
・APIによってはイベントリスナでしか対応してないもの
上記かなっと思います。(まだまだありそうですが...)
ハンドラを追加する構文は次のようになります。
element.addEventListener(event, handler[, phase]);
まず最初のelement
部分はEventTarget
と言われ、イベントを設定する所を
指定しています。よく指定されるのが、
・Window
・document
・element( DOMにidとかつけてdocument.getElementById("hoge")とかで取得してくるものとか )
が多いですね。他にもありますので気になる方は次のドキュメントで確認ください。
参考:EventTargetリファレンスサイト
次に引数部分ですが
event
イベント名です。 click
keydown
...etc
参考:JavaScriptのイベントハンドラ一覧
handler
ハンドラ関数.
phase
オプションの引数で、ハンドラが動作する “フェーズ” です。基本は使いません。ただ処理速度とか考えるなら知ってるほうがいいです。(以下参考記事)
参考:addEventListener の第3引数が拡張されてるという話
これらを使用してイベントリスナを登録していきます。
イベントリスナ
addEventListener メソッドを使用してイベントに対して登録した関数のこと
ハンドラ
イベント発生時に実行する関数のこと
次に登録後、不要になったので削除する場合ですね。
removeEventListener
削除するにはremoveEventListener()
メソッドを使います。
構文は次になります。
// addEventListener とまったく同じ引数にする必要があります。
element.removeEventListener(event, handler[, phase]);
削除する時はaddEventListener
とまったく同じ引数にしないと登録したイベントリスナは削除されません。
例:削除されないパターン
// 追加
document.addEventListener( "click" , () => alert('hoge'));
// 削除
document.removeEventListener( "click", () => alert('hoge'));
イベントリスナハンドラは削除されません。removeEventListener は別の関数を取得しているためです。同じコードでも関係ありません。
例:削除されるパターン
// ハンドラ
const handler = () => {
alert( 'hoge' );
}
// 作成
document.addEventListener("click", handler);
// 削除
document.removeEventListener("click", handler);
変数に関数を所持することで削除が可能になります。逆に言いますと変数で関数を所持しないと削除はできなくなります。
参考:現代のJavaScript : addEventListener
ドキュメントや他webサイトに上記の内容が書いてあるんですけど、ただReactで素直に設定しようとすると落とし穴にはまってしまうんですね。(自分ははまりました(笑))
Reactでのイベントリスナの設定
素直にイベントリスナを設定した場合(失敗例)
イベントリスナ設定のサンプルコードを作っていきます。 今回はドキュメント押下したらカウントアップするハンドラを使用します。
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>
);
}
addEventListener
ボタンを押下したらイベントリスナが登録され、removeEventListener
で削除できるように作ったつもりです。
削除する時に気をつける、引数一緒も意識しました。(削除はされません)
addEventListener
のボタンを押下する度にイベントリスナが追加されています。
removeEventListener
のボタンでイベントリスナが削除されていません。
引数は同じなのに削除できないのが不思議です。
Reactの性質について考えて作り直したもの(成功例)
Reactは状態が変化すると再レンダリングします。レンダリング処理が走る度にコンポーネント内の関数が新規で作成されているのです。
という事は同じ変数名の関数でも厳密に見ると違う関数になるという事です。
レンダリングの度に違う関数....、state使ってるから外にも書けないし....。
となりましたが、reactに優秀なhooksがいます。関数をメモ化すれば新規で作られる事はありません。
useCallback
今回はこのhooksを使用して関数をメモ化していきます。参考:React hooksを基礎から理解する (useCallback編+ React.memo)
作り直したサンプルコード
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
を使うとしっかりと分岐の判定をしてくれます。
ポイント
Reactでイベントリスナーを設定する場合は関数(ハンドラ)をメモ化しましょう。
Reactでページ遷移するタイミグで解除する方法
遷移する時はuseEffect
を使用すると遷移するタイミングで解除できます。
// カウントをアップする(ハンドラ)
const countUp = useCallback(()=>{
setCount(count => {
count++;
return count;
})
},[]);
// イベントリスナーの設定
useEffect(() => {
// イベントの設定
document.addEventListener('click', countUp);
return () => {
// イベントの設定解除
document.removeEventListener('click', countUp);
};
}, []);
上のコードの例ですとレンダリングされた時にイベントリスナを設定して、ページ遷移するタイミングでイベントリスナを削除します。
こんな形で遷移時に追加したいイベントを書きます。
終わり
説明不足もあると思いますが、イベントリスナの設定を自分なりにまとめられてよかったです。