使用React Hooks在窗口上添加和删除mousemove监听器

问题描述

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

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

添加一个useEffect来观看isCardMoving。启用isCardMoving时,应在触发mousemove函数的窗口中添加一个handleCardMove事件监听器。此功能仅记录鼠标的坐标。

如果再次单击该卡,则isCardMoving将为假,并且我希望窗口上的事件侦听器将在useEffect内被删除

但是,发生的事情是,事件监听器将在isCardMovingtrue时被添加,而一旦isCardMovingfalse时将不会被删除

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)} />;
};

然后我试图在窗口上设置ref,以为可能出于某种原因我可能需要对该窗口的先前引用:

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',y: event.offsetY });

  return <Card onClick={() => setIsCardMoving(!isCardMoving)} />;
};

这似乎和以前一样。

解决方法

实际上,您不能在React或任何其他基于虚拟DOM的应用程序中删除这样的事件侦听器。由于虚拟DOM库的性质,您必须在卸载生命周期中删除事件侦听器,该生命周期位于React钩子中,可在useEffect本身中使用。因此,您必须像下面那样使用return关键字来执行此操作,它将与类基础组件中的componentWillUnmount相同:

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

工作演示:

CodeSandbox

更新

正如@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",[isCardMoving,handleCardMove]);

工作演示:

CodeSandbox