この記事を作った動機
React で addEventListener を使ったら、再描画される事に、addEventListener されまくって、例えばマウスボタンが押されたというイベントが発火したときに、無数の同じ処理が走りまくるという事態になった。
これは以下のように、react の要素の一部として書いたときには、起こらなかった。
export function OverlayWindow({ children, arg }:{ children:ReactNode, arg:OverlayWindowArgs }){
// 何かしらのコード
if(visible){
return (<div className={OverlayWindowContaierClassName} style={windowPosStyle}>
<div className="windowHeader move bg-yellow-600 w-full h-[2rem] justify-center place-items-center align-middle text-center"
onMouseDown={windowHandlers.mousedown} // addEventListener 相当
onTouchStart={windowHandlers.touchstart} // addEventListener 相当
>
<div className="title h-[1rem] absolute text-white">{arg.title}</div>
<div className="close size-[2rem] bg-red-700 ml-auto" onClick={() => {setVisible(false)}}></div>
</div>
<div className="content bg-gray-900 min-h-[5rem] w-full flex justify-center place-items-center align-middle text-center
items-center">
{children}
</div>
</div>)
}
}
それでとりあえず、解決状態だと思われる、アプリ自体の動作を重くしないレベルの実装にもっていくまでについて、簡易的に記録を取ろうと思い、この記事を作った。
環境
- Vite
- React
- TypeScript
- Tailwind CSS
- zustand
問題の状態
このようにして、グローバルにすべての mousemove などのイベントの発火を要素関係なく受け取ろうとすると、再描画時に何度も addEventListener で特定のイベントに対し、コールバック関数が登録されてしまう。
export function OverlayWindow({ children, arg }:{ children:ReactNode, arg:OverlayWindowArgs }){
// 何かしらのコード
addEventListener("touchend",windowHandlers.touchend)
addEventListener("mouseup",windowHandlers.mouseup)
addEventListener("touchmove",windowHandlers.touchmove)
addEventListener("mousemove",windowHandlers.mousemove)
// 何かしらのコード
}
私が今回取った対応
useRef を使って、コンポーネントが初期化されたかどうかに関する状態を管理することにした。そうすることで一回だけ addEventListener で特定のイベントにコールバック関数を設定するようにした。
export function OverlayWindow({ children, arg }:{ children:ReactNode, arg:OverlayWindowArgs }){
// 何かしらのコード
let init = useRef(true)
// 何かしらのコード
if(init.current){
// console.log("Overlay window init")
addEventListener("touchend",windowHandlers.touchend)
addEventListener("mouseup",windowHandlers.mouseup)
addEventListener("touchmove",windowHandlers.touchmove)
addEventListener("mousemove",windowHandlers.mousemove)
init.current = false
console.log(init.current)
}
// 何かしらのコード
}
更に新しい対応(2025/10/5 追記)
最近、useEffect でもっときれいに実装できることがわかったので、どうするのか記録する。
export function OverlayWindow({ children, arg }:{ children:ReactNode, arg:OverlayWindowArgs }){
// 何かしらのコード
// 依存関係を設定していない useEffect はコンポーネント生成時に一度だけ実行される。
// またコンポーネント再生成のときなど、コンポーネント破棄時には、return に関数が設定されていれば、それが実行される。
useEffect(() => {
addEventListener("touchend",windowHandlers.touchend)
addEventListener("mouseup",windowHandlers.mouseup)
addEventListener("touchmove",windowHandlers.touchmove)
addEventListener("mousemove",windowHandlers.mousemove)
return () => {
removeEventListener("touchend",windowHandlers.touchend)
removeEventListener("mouseup",windowHandlers.mouseup)
removeEventListener("touchmove",windowHandlers.touchmove)
removeEventListener("mousemove",windowHandlers.mousemove)
}
},[]) // ちなみに、空でもいいので、依存関係を表す配列がないと、コンポーネント再描画時に毎回実行される useEffect ができてしまい、全く意味の違うものになるので注意する。
// 何かしらのコード
}
関連の記事
参考にしたサイトとか
- EventTarget: addEventListener() method - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener (2025年9月10日) - useEffect – React
https://react.dev/reference/react/useEffect (2025年9月9日) - useRef – React
https://react.dev/reference/react/useRef (2025年9月9日) - useState – React
https://react.dev/reference/react/useState (2025年9月9日) - useState – React
https://react.dev/reference/react/useState#updating-objects-and-arrays-in-state (2025年9月9日) - ChatGPT
https://chatgpt.com/ (2025年9月9日)