React中removeEventListener()注意事项

2年前 (2022) 程序员胖胖胖虎阿
262 0 0

众所周知,事件监听器这种东西,除非是{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

版权声明:程序员胖胖胖虎阿 发表于 2022年10月5日 上午9:00。
转载请注明:React中removeEventListener()注意事项 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...