如何在 React 中删除 eventListener?

问题描述 投票:0回答:1

我不能删除

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
不应该是必要的。这是一个失败的测试。

javascript reactjs react-hooks event-listener
1个回答
4
投票

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

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

为什么那不起作用

您所拥有的将用于first渲染,但不适用于后续渲染。 (如果您使用的是 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!
。但是如果你点击按钮进行第二次渲染,你会看到这个序列:

 添加事件监听器
删除事件侦听器

注意顺序。它重新添加事件侦听器(它不执行任何操作,因为您不能多次将同一事件的相同事件侦听器函数添加到同一元素),然后在渲染之后进行

useEffect
清理previous 渲染运行,删除事件侦听器。这隐含在
useEffect
cleanup
的工作方式中,但它看起来有点令人惊讶。

有趣的是,如果您 没有 记住事件监听器,它会起作用,因为在添加时,它会短暂地添加第二个事件监听器,然后第一个会被

useEffect
清理删除。

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>

但是不要那样做。除了调用挂钩之外,您的

render
函数应该是纯函数(它不应该有有意义的副作用)。添加事件侦听器是一个有意义的副作用。

副作用是

useEffect
的重点(更多信息)。因此,让我们以标准方式进行操作,通过在
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
性能优化,而不是语义保证。
useCallback
useMemo
的包装,它有这个免责声明(他们的重点)您可能依赖 useMemo 作为性能优化,而不是语义保证。 但您的代码依赖它作为语义保证。)

© www.soinside.com 2019 - 2024. All rights reserved.