如何删除 React 组件中的 window eventListener?

How to remove window eventListener in React component?

这段代码显示了用户鼠标坐标,它工作正常。

但我还想通过单击“停止”按钮删除事件处理程序。不幸的是,这似乎是错误的,因为即使在单击按钮后,事件处理程序仍在跟踪和更改 position:

import { useEffect, useState } from 'react'

function MouseCords() {
    const [position, setPosition] = useState({ x: 0, y: 0 })

    function mouseMoveHandler(event) {
        setPosition({
            x: event.clientX,
            y: event.clientY
        })
    }

    useEffect(() => {
        window.addEventListener('mousemove', mouseMoveHandler)
    }, [])

    return (
        <div>
            <pre>
                x: {position.x}
                <br />
                y: {position.y}
            </pre>
            <button
                onClick={() =>
                    window.removeEventListener('mousemove', mouseMoveHandler)
                }
            >
                STOP
            </button>
        </div>
    )
}

我猜 context 可能有问题,但我不确定如何解决它。

顺便说一句,这是 React 组件,我在 App.js 中使用它是这样的:

import MouseCords from './components/MouseCords'

function App() {
    return (
        <div>
            <MouseCords />
        </div>
    )
}

export default App
useEffect(() => {
        window.addEventListener('mousemove', mouseMoveHandler)

return window.removeEventListener('mousemove', mouseMoveHandler)
    }, [])

在组件顶层声明的函数将在每次渲染时重新声明,按钮 onClick 属性 中的匿名函数也是如此。因此,当您尝试删除侦听器时,点击处理程序中的函数引用将与设置侦听器的函数引用不匹配,因此它不会起作用。

最简单的解决方案是使用 useCallback 声明函数,这将确保函数仅在第一次渲染时声明(由于依赖项数组为空),因此点击处理程序和 useEffect 函数引用将匹配.

您还应该始终通过从 useEffect 返回一个函数来提供一个 'cleanup' function ,该函数将在卸载时清理任何 side-effects 我在组件生命周期中引起的 useEffect .

const { useState, useEffect, useCallback } = React;

function MouseCoords() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const mouseMoveHandler = useCallback((event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  }, []);

  useEffect(() => {
    window.addEventListener('mousemove', mouseMoveHandler);
    
    return () => {
      window.removeEventListener('mousemove', mouseMoveHandler);
    };
  }, []);

  return (
    <div>
      <pre>
        x: {position.x}
        <br />
        y: {position.y}
      </pre>
      <button
        onClick={() =>
          window.removeEventListener('mousemove', mouseMoveHandler)
        }
      >
        STOP
      </button>
    </div>
  )
}

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<MouseCoords />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

<div id='root'></div>

或者,您可以完全通过 useEffect 处理侦听器,并在每次更改时使用布尔标志有条件地 add/remove 侦听器。在这种情况下,每次依赖数组发生变化时,'cleanup' 将 运行。

const { useState, useEffect, useCallback } = React;

function MouseCoords() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [shouldTrack, setShouldTrack] = useState(true);

  const mouseMoveHandler = useCallback((event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  }, []);

  useEffect(() => {
    if (shouldTrack) {
      window.addEventListener('mousemove', mouseMoveHandler);
    
      return () => {
        window.removeEventListener('mousemove', mouseMoveHandler);
      };
    }
  }, [shouldTrack]);

  return (
    <div>
      <pre>
        x: {position.x}
        <br />
        y: {position.y}
      </pre>
      <button
        onClick={() => setShouldTrack(b => !b)}
      >
        {shouldTrack ? 'Stop' : 'Start'}
      </button>
    </div>
  )
}

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<MouseCoords />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

<div id='root'></div>