使用 React Hooks 在 window 上添加和删除 mousemove 侦听器

Add and remove mousemove listener on window with React Hooks

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

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

我添加了一个useEffect来观看isCardMoving。当 isCardMoving 开启时,它应该添加一个 mousemove 事件侦听器到 window 触发 handleCardMove 函数。这个函数只是记录鼠标的坐标。

如果我再次单击该卡片,isCardMoving 将为假,我希望 window 上的事件侦听器在 useEffect.[=28= 中被删除]

不过,事件侦听器将在 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)} />;
};

然后我尝试在 window 上设置一个 ref,我想我可能出于某种原因需要先前对 window 的引用:

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

这个好像和之前的效果一样

实际上,您不能在 React 或任何其他虚拟 DOM-based 应用程序中删除这样的事件侦听器。由于虚拟 DOM 库的性质,您必须在 React 挂钩中的卸载生命周期中删除事件侦听器,它在 useEffect 本身中可用。所以你必须像下面那样使用 return 关键字,它将与 class 基本组件中的 componentWillUnmount 做同样的事情:

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

工作演示:

更新

正如@ZacharyHaber 在评论中所说,此行为背后的主要原因是因为您的 handleCardMove 函数将在每次渲染时重新定义,因此为了克服这种情况,我们需要解除事件与 window 在每个渲染器上使用 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]);

工作演示: