reactjs 如何删除React中的eventListener?

kmbjn2e3  于 2023-03-17  发布在  React
关注(0)|答案(1)|浏览(166)

我不能删除eventListener,即使使用React.useCallback也不行。那么,我现在该怎么办?
下面是我的代码,位于组件类的开头:

const noCursorEventListener = React.useCallback((e) => {
    console.log('ncel');
    let lista = document.getElementsByClassName('lista');
    if (lista && lista[0]) lista[0].classList.remove('nocursor');
}, []);

window.addEventListener('mousemove', noCursorEventListener);

用于删除它的useEffect:

useEffect(() => {
    return () => {
        window.removeEventListener('mousemove', noCursorEventListener);
        window.onmousemove = null;
        console.log('remove el');
    }
});

我看到的remove el是正确的,但是在那之后,在页面更改之后,我仍然收到ncel消息。我该如何修复它?
那个window.onmousemove = null不应该是必要的,它是一个失败的测试。

c86crjj0

c86crjj01#

TL;DR不要在render中做(非钩子)副作用。:-)对于潜伏者,如果你只是想知道如何使用DOM方法正确地添加事件侦听器(在极少数情况下是合适的),请参阅下面的标准方法

但对于那些对OP的代码为什么不起作用感兴趣的人来说:

为什么不起作用

您所拥有的将适用于 * 第一次 * 渲染,但不适用于后续渲染。(如果您使用React的StrictMode,它可能在开始时渲染了两次。) 您可以看到为什么我们在发生的每个阶段都记录一条消息(我将mousemove更改为click,因为这无关紧要,而且避免了日志混乱):

const { useState, useEffect } = React;

const Example = () => {
    const noCursorEventListener = React.useCallback((e) => {
        console.log("event listener called!");
    }, []);

    console.log("Adding event listener");
    window.addEventListener("click", noCursorEventListener);

    useEffect(() => {
        return () => {
            console.log("Removing event listener");
            window.removeEventListener("click", noCursorEventListener);
        };
    });

    const [counter, setCounter] = useState(0);
    const increment = (event) => {
        setCounter(c => c + 1);
        event.stopPropagation();
    };

    return (
        <div>
            {counter} <input type="button" value="+" onClick={() => setCounter((c) => c + 1)} />
        </div>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

如果运行该命令,您将看到Adding event listener,因为渲染器添加了事件侦听器。如果单击按钮以外的其他位置,您将看到event listener called!。但如果单击按钮进行第二次渲染,您将看到以下序列:

Adding event listener
Removing event listener

注意顺序。它重新添加了事件侦听器(这不做任何事情,因为你不能为同一个事件向同一个元素添加同一个事件侦听器函数超过一次),然后在渲染后为 * 前一次 * 渲染运行useEffect清理,删除事件侦听器。这在useEffect清理的工作方式中是隐含的,但看起来有点令人惊讶。

有趣的是,如果你没有记住事件侦听器,它会工作,因为当添加时,它会短暂地添加第二个事件侦听器,然后第一个会被useEffect清理删除。
一个三个三个一个
但是不要这样做。除了调用钩子,你的render函数应该是纯的(它不应该有有有意义的副作用)。添加一个事件监听器是一个有意义的副作用。
副作用是useEffectmore here)的关键所在,所以让我们用标准的方法来做,在useEffect回调函数中连接侦听器,并在清除该效果时删除同一个处理程序(这也意味着我们不必在每次丢弃侦听器函数时都创建一个新的侦听器函数)。

标准方式

下面是在挂载时添加事件侦听器并在卸载时删除它的标准方法,适用于那些相对较少的用例,直接使用DOM执行此操作比较合适:

useEffect(() => {
    const noCursorEventListener = (e) => {
        let lista = document.getElementsByClassName("lista");
        if (lista && lista[0]) lista[0].classList.remove("nocursor");
    };

    window.addEventListener("mousemove", noCursorEventListener);
    return () => {
        window.removeEventListener("mousemove", noCursorEventListener);
    };
}, []); // <== Empty dependencies array = only run effect on mount

(还有一个单独的问题:useCallback是 * 性能优化 ,而不是语义保证。useCallbackuseMemo的 Package 器,它有以下免责声明 (强调)"您可能依赖useMemo作为性能优化,而不是作为语义保证。"* 但您的代码依赖它作为语义保证。)

相关问题