使用 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= 中被删除]
不过,事件侦听器将在 isCardMoving
为 true
时添加,然后在 isCardMoving
为 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)} />;
};
然后我尝试在 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]);
工作演示:
我试图在单击某个对象时在 window
上添加一个事件侦听器,然后在再次单击该对象时删除该事件侦听器。
单击 Card
组件时,状态 isCardMoving
会打开或关闭。
我添加了一个useEffect
来观看isCardMoving
。当 isCardMoving
开启时,它应该添加一个 mousemove
事件侦听器到 window 触发 handleCardMove
函数。这个函数只是记录鼠标的坐标。
如果我再次单击该卡片,isCardMoving
将为假,我希望 window 上的事件侦听器在 useEffect
.[=28= 中被删除]
不过,事件侦听器将在 isCardMoving
为 true
时添加,然后在 isCardMoving
为 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)} />;
};
然后我尝试在 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]);
工作演示: