众所周知,事件监听器这种东西,除非是{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
相关文章
暂无评论...
