使用 React Hooks 在窗口上添加和删除鼠标移动侦听器

rpi*_*var 0 addeventlistener mousemove reactjs react-hooks removeeventlistener

我试图在window单击对象时添加一个事件侦听器,然后在再次单击该对象时删除该事件侦听器。

Card单击组件时,状态isCardMoving会打开或关闭。

我添加了一个useEffect来观看isCardMoving。当isCardMoving打开时,它应该向mousemove触发该handleCardMove功能的窗口添加一个事件侦听器。该函数仅记录鼠标的坐标。

如果我再次单击该卡,isCardMoving将为 false,并且我希望窗口上的事件侦听器在useEffect.

isCardMoving但会发生什么情况,事件侦听器将在is时添加true,然后一旦isCardMovingis就不会被删除false

import React from 'react';

const App = () => {
  const [isCardMoving, setIsCardMoving] = React.useState(false);

  React.useEffect(() => {
    if (isCardMoving) window.addEventListener('mousemove', handleCardMove);
    else window.removeEventListener('mousemove', handleCardMove);
  }, [isCardMoving]);

  const handleCardMove = (event) => console.log({ x: event.offsetX, y: event.offsetY });

  return <Card onClick={() => setIsCardMoving(!isCardMoving)} />;
};
Run Code Online (Sandbox Code Playgroud)

然后我尝试ref在窗口上设置 a ,认为由于某种原因我可能需要先前引用该窗口:

import React from 'react';

const App = () => {
  const [isCardMoving, setIsCardMoving] = React.useState(false);

  const windowRef = React.useRef(window); // add window ref

  // update window ref whenever window is updated
  React.useEffect(() => {
    windowRef.current = window;
  }, [window]);

  React.useEffect(() => {
    // add and remove event listeners on windowRef
    if (isCardMoving) windowRef.current.addEventListener('mousemove', handleCardMove);
    else windowRef.current.removeEventListener('mousemove', handleCardMove);
  }, [isCardMoving]);

  const handleCardMove = (event) => console.log({ x: event.offsetX, y: event.offsetY });

  return <Card onClick={() => setIsCardMoving(!isCardMoving)} />;
};
Run Code Online (Sandbox Code Playgroud)

这看起来和之前的效果是一样的。

SMA*_*KSS 8

您无法在 React 或其他基于虚拟 DOM 的应用程序中删除这样的事件侦听器。由于虚拟 DOM 库的性质,您必须在卸载生命周期中删除事件侦听器,该事件侦听器位于 React hooks 中并且在其useEffect自身中可用。所以你必须像下面那样使用 return 关键字来执行此操作。它将执行与componentWillUnmount类​​基组件中相同的操作:

React.useEffect(() => {
    if (isCardMoving) window.addEventListener("mousemove", handleCardMove);
    return () => window.removeEventListener("mousemove", handleCardMove);
}, [isCardMoving]);
Run Code Online (Sandbox Code Playgroud)

工作演示:

代码沙盒

更新

正如 @ZacharyHaber 在评论中所说,这种行为背后的主要原因是您的handleCardMove函数将在每次渲染时重新定义,因此为了克服这种情况,我们需要使用回调在每次渲染时从窗口中解除事件绑定useEffect。您还可以使用该useCallback方法使您的初始代码正常工作。尽管如此,您还需要将前一个useEffect回调添加到组件中,以确保事件侦听器将在组件卸载周期中删除,这需要更多的编码,但是这个回调将与上述方法执行相同的操作。

const handleCardMove = React.useCallback((event) => {
   console.log({ x: event.offsetX, y: event.offsetY });
}, []);

React.useEffect(() => {
  if (isCardMoving) window.addEventListener("mousemove", handleCardMove);
  else window.removeEventListener("mousemove", handleCardMove);
  return () => window.removeEventListener("mousemove", handleCardMove);
}, [isCardMoving, handleCardMove]);
Run Code Online (Sandbox Code Playgroud)

工作演示:

代码沙盒

  • 与虚拟 dom 库如何工作无关,更多的是在每次渲染时重新定义“handleCardMove”。删除事件侦听器需要最初添加的函数的句柄。例如,使用 `useCallback((e)=&gt;console.log(e),[])` 将使初始代码可以工作 (3认同)
  • @rpivovar 是的,扎卡里确实是对的。如果将函数放入 `useCallBack` 并将其添加到 `useEffect` 依赖项数组中,则可以使用它而无需使用 `return` 并使用您尝试的初始方法。 (2认同)
  • 不过,不要这样做,因为这样在组件卸载时事件侦听器不会被清理,所以这不是理想的方法。因此,为什么我只是将“useCallback”方法作为注释进行评论,而不是提出单独的答案。这个答案是正确的做法,只是其背后的想法略有不同:) (2认同)