眾所周知,事件監聽器這種東西,除非是{once: true}這種一次性監聽器,否則凡註冊的,必移除。
但是在React的Hook組件中,移除事件監聽時卻要留點神,否則可能代碼寫得自認為OK,實際一點效果都沒有。
先來看個例子:
一個計數器,初始值為1,每點擊一次按鈕,數字+1,噹噹前數字為偶數時,註冊監聽器,為奇數時,移除監聽器。
寫法一
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [c, setC] = useState(1);
useEffect(() => {
if (c % 2 === 0) {
document.getElementById('root').addEventListener("keyup", (e) => {
console.log(e.key);
});
} else {
document.getElementById('root').removeEventListener("keyup", (e) => {
console.log(e.key);
});
}
}, [c]);
return (
<div className="App">
<h1>{c}</h1>
<button
onClick={(e) => {
setC(c + 1);
}}
>
點我加一
</button>
</div>
);
}
測試結果:
可以註冊,無法移除。
寫法二
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [c, setC] = useState(1);
useEffect(() => {
function f(e) {
console.log(e.key);
}
if (c % 2 === 0) {
document.getElementById("root").addEventListener("keyup", (e) => {
f(e);
});
} else {
document.getElementById("root").addEventListener("keyup", (e) => {
f(e);
});
}
}, [c]);
return (
<div className="App">
<h1>{c}</h1>
<button
onClick={(e) => {
setC(c + 1);
}}
>
點我加一
</button>
</div>
);
}
測試結果:
可以註冊,無法移除。
寫法三
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [c, setC] = useState(1);
useEffect(() => {
const f = (e) => {
console.log(e.key);
};
if (c % 2 === 0) {
document.getElementById("root").addEventListener("keyup", f);
} else {
document.getElementById("root").removeEventListener("keyup", f);
}
}, [c]);
return (
<div className="App">
<h1>{c}</h1>
<button
onClick={(e) => {
setC(c + 1);
}}
>
點我加一
</button>
</div>
);
}
測試結果:
可以註冊,無法移除。
為什麼 ?
一二失敗很好理解,因為匿名函數不支持清除。
但是三為什麼也失敗?
因為組件隨着數字的變化在不斷地重新渲染。
數字每變換一次,組件就渲染一次,這樣定義在組件內部的函數其內存也一直在變化。
所以,當數字為奇數時,被移除的監聽器是一個從未被註冊過的監聽器。
而我們真正想要移除的那個監聽器的內存已經不可獲取了。
所以出現了只能註冊,無法移除的現象。
解決
既然如此,那就只能在組件當前的這個生命週期內實現註冊和移除。
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [c, setC] = useState(1);
useEffect(() => {
const f = (e) => {
console.log(e.key);
};
if (c % 2 === 0) {
document.getElementById("root").addEventListener("keyup", f);
}
return () => {
document.getElementById("root").removeEventListener("keyup", f);
};
}, [c]);
return (
<div className="App">
<h1>{c}</h1>
<button
onClick={(e) => {
setC(c + 1);
}}
>
點我加一
</button>
</div>
);
}
測試結果:
可以註冊,可以移除。
總結
不要試圖去移除一個回調函數為匿名函數的監聽器
監聽器的註冊和移除不可跨越組件的生命週期
點擊體驗本文示例:
beCarefuleToremoveEventListener