博客 / 詳情

返回

React中removeEventListener()注意事項

眾所周知,事件監聽器這種東西,除非是{once: true}這種一次性監聽器,否則凡註冊的,必移除。

但是在ReactHook組件中,移除事件監聽時卻要留點神,否則可能代碼寫得自認為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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.