使用 React 钩子时事件侦听器功能发生变化
Event listener functions changing when using React hooks
我有一个组件通过 addEventListener
和 removeEventListener
在多个地方使用事件侦听器。使用像 onMouseMove
这样的组件方法是不够的,因为我还需要检测组件外部的事件。
我在组件中使用了挂钩,其中一些挂钩在末尾有依赖数组,特别是 useCallback(eventFunction, dependencies)
带有要与侦听器一起使用的事件函数。依赖项通常是使用 useState
.
声明的有状态变量
据我所知,函数的标识在 add/remove
EventListener
中很重要,因此如果函数在两者之间发生变化,它就不起作用。起初我尝试管理挂钩,以便事件函数不会在 add
和 remove
之间改变身份,但随着函数对状态的依赖性,这很快变得笨拙。
所以最后我想到了以下模式:由于 setter 函数(useState
的第二个输入参数)获取当前状态作为参数,我可以有事件在第一次渲染后永远不会改变的函数(我们还称这个为挂载吗?)但仍然可以访问最新的有状态变量。一个例子:
import React, { useCallback, useEffect, useState } from 'react';
const Component = () => {
const [state, setState] = useState(null);
const handleMouseMove = useCallback(() => {
setState((currentState) => {
// ... do something that involves currentState ...
return currentState;
});
}, []);
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [/* ... some parameters here ... */]);
// ... more effects etc ...
return <span>test</span>;
};
(这是一个简化得多的图示)。
这似乎工作正常,但我不确定它感觉是否正确 - 使用从不更改状态但只是作为访问当前状态的 hack 的 setter 函数。
此外,对于需要多个状态变量的事件函数,我必须嵌套 setter 调用。
是否有另一种模式可以更好地处理这种情况?
From what I can tell, the identity of the function is significant in add/remove EventListener, so that if the function changes in between it doesn't work.
虽然这是事实,但我们不必极端地安排 even 函数根本不改变身份。
简单的步骤是:
使用 useCallback
声明事件函数 - useCallback
的依赖列表应包括您的函数所依赖的所有有状态变量。
使用useEffect
添加事件侦听器。 Return 清除事件监听器的函数。 useEffect
的依赖列表应该包括事件侦听器函数本身,以及您的效果函数可能使用的任何其他有状态变量。
这样,当事件侦听器使用的任何状态变量发生变化时,即使侦听器的身份发生变化,也会触发运行ning效果,但在运行ning之前返回效果清理函数之前 运行 的效果将是 运行,在添加新事件监听器之前正确删除旧事件监听器。
类似于:
const Component = () => {
const [state, setState] = useState();
const eventListner = useCallback(() => {
console.log(state); // use the stateful variable in event listener
}, [state]);
useEffect(() => {
el.addEventListner('someEvent', eventListner);
return () => el.removeEventListener('someEvent', eventListner);
}, [eventListener]);
}
@ckedar的方案可以解决这个问题,但是存在性能问题,当eventListener变化时,react会移除并在dom上添加Event。
你可以用useRef()
代替useState()
,如果你想改变监听状态,你可以用useStateRef()
:
import React, { useEffect, useRef, useState } from 'react';
export default function useStateRef(initialValue:any): Array<any>{
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
},[value])
return [value,setValue,ref];
}
我有一个组件通过 addEventListener
和 removeEventListener
在多个地方使用事件侦听器。使用像 onMouseMove
这样的组件方法是不够的,因为我还需要检测组件外部的事件。
我在组件中使用了挂钩,其中一些挂钩在末尾有依赖数组,特别是 useCallback(eventFunction, dependencies)
带有要与侦听器一起使用的事件函数。依赖项通常是使用 useState
.
据我所知,函数的标识在 add/remove
EventListener
中很重要,因此如果函数在两者之间发生变化,它就不起作用。起初我尝试管理挂钩,以便事件函数不会在 add
和 remove
之间改变身份,但随着函数对状态的依赖性,这很快变得笨拙。
所以最后我想到了以下模式:由于 setter 函数(useState
的第二个输入参数)获取当前状态作为参数,我可以有事件在第一次渲染后永远不会改变的函数(我们还称这个为挂载吗?)但仍然可以访问最新的有状态变量。一个例子:
import React, { useCallback, useEffect, useState } from 'react';
const Component = () => {
const [state, setState] = useState(null);
const handleMouseMove = useCallback(() => {
setState((currentState) => {
// ... do something that involves currentState ...
return currentState;
});
}, []);
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [/* ... some parameters here ... */]);
// ... more effects etc ...
return <span>test</span>;
};
(这是一个简化得多的图示)。
这似乎工作正常,但我不确定它感觉是否正确 - 使用从不更改状态但只是作为访问当前状态的 hack 的 setter 函数。
此外,对于需要多个状态变量的事件函数,我必须嵌套 setter 调用。
是否有另一种模式可以更好地处理这种情况?
From what I can tell, the identity of the function is significant in add/remove EventListener, so that if the function changes in between it doesn't work.
虽然这是事实,但我们不必极端地安排 even 函数根本不改变身份。
简单的步骤是:
使用 useCallback
声明事件函数 - useCallback
的依赖列表应包括您的函数所依赖的所有有状态变量。
使用useEffect
添加事件侦听器。 Return 清除事件监听器的函数。 useEffect
的依赖列表应该包括事件侦听器函数本身,以及您的效果函数可能使用的任何其他有状态变量。
这样,当事件侦听器使用的任何状态变量发生变化时,即使侦听器的身份发生变化,也会触发运行ning效果,但在运行ning之前返回效果清理函数之前 运行 的效果将是 运行,在添加新事件监听器之前正确删除旧事件监听器。
类似于:
const Component = () => {
const [state, setState] = useState();
const eventListner = useCallback(() => {
console.log(state); // use the stateful variable in event listener
}, [state]);
useEffect(() => {
el.addEventListner('someEvent', eventListner);
return () => el.removeEventListener('someEvent', eventListner);
}, [eventListener]);
}
@ckedar的方案可以解决这个问题,但是存在性能问题,当eventListener变化时,react会移除并在dom上添加Event。
你可以用useRef()
代替useState()
,如果你想改变监听状态,你可以用useStateRef()
:
import React, { useEffect, useRef, useState } from 'react';
export default function useStateRef(initialValue:any): Array<any>{
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
},[value])
return [value,setValue,ref];
}