【问题标题】:Event listener functions changing when using React hooks使用 React 钩子时事件侦听器函数发生变化
【发布时间】:2023-04-02 13:16:02
【问题描述】:

我有一个组件通过addEventListenerremoveEventListener 在多个地方使用事件侦听器。使用 onMouseMove 这样的组件方法是不够的,因为我还需要检测组件之外的事件。

我在组件中使用了钩子,其中有几个在末尾有依赖数组,特别是useCallback(eventFunction, dependencies) 带有要与侦听器一起使用的事件函数。依赖项通常是使用useState 声明的有状态变量。

据我所知,函数的标识在add/remove EventListener 中很重要,因此如果函数在两者之间发生变化,它就不起作用。起初我尝试管理钩子,以便事件函数不会在 addremove 之间更改身份,但由于函数对状态的依赖,这很快变得笨拙。

所以最后我想出了以下模式:由于 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>;
};

(这是一个非常简化的插图)。

这似乎工作正常,但我不确定它是否正确 - 使用永远不会改变状态但只是作为访问当前状态的黑客的 setter 函数。

另外,对于需要多个状态变量的事件函数,我必须嵌套 setter 调用。

是否有另一种模式可以更好地处理这种情况?

【问题讨论】:

  • 你能不能只在这里使用state作为那是当前状态。您是否设置状态以触发重新渲染组件的反应?
  • 如果我这样做,我必须确保onMouseMovestate 更改时更新,即在依赖数组中提供state。然后对removeEventListener的调用将不起作用,因为它不再是相同的效果函数
  • 请参阅 render props 作为替代方案。
  • 有些事情并没有完全加起来,onMouseMove 应该只依赖于鼠标在事件侦听器附加到的任何元素中移动,这里是窗口。这里的目标是什么?
  • 目标是一个允许拖拽选择子组件的组件,并且也只根据滚动渲染后者的一个窗口。 onMouseMove 取决于对子组件的引用列表(用于检查它们的位置,使用 getBoundingClientRect

标签: javascript reactjs addeventlistener react-hooks usecallback


【解决方案1】:

@ckedar 的方案可以解决这个问题,但是存在性能问题,当eventListener 发生变化时,react 会在dom 上remove 和addEvent。

你可以用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];
}

【讨论】:

    【解决方案2】:

    据我所知,函数的标识在添加/删除 EventListener 中很重要,因此如果函数在两者之间发生变化,它就不起作用。

    虽然这是真的,但我们不必走极端来安排偶数函数根本不改变身份。

    简单的步骤是: 使用 useCallback 声明事件函数 - useCallback 的依赖列表应包括您的函数所依赖的所有有状态变量。

    使用useEffect 添加事件监听器。返回将删除事件侦听器的清理函数。 useEffect 的依赖关系列表应包括事件侦听器函数本身,以及您的效果函数可能正在使用的任何其他有状态变量。

    这样,当事件监听器使用的任何状态变量发生变化时,即使监听器的身份发生变化,也会触发效果的运行,但在运行效果之前,会运行之前运行效果返回的清理函数,正确移除添加新事件侦听器之前的旧事件侦听器。

    以下内容:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-02
      • 2015-05-23
      • 2019-11-27
      • 2019-05-19
      • 2012-07-03
      • 2023-02-09
      • 1970-01-01
      • 2018-09-04
      相关资源
      最近更新 更多