React/Redux、useEffect/useSelector、过时闭包和事件监听器

mik*_*ell 2 javascript reactjs redux react-hooks use-effect

我有一个 React/Redux 类组件,我正在将其转换为功能组件。

\n

以前,它有一个componentDidMount回调,为从应用程序中其他地方调度的自定义事件添加事件侦听器。这正在被替换useEffect。事件侦听器在触发时调用该组件中其他位置的方法。

\n

此方法执行的操作取决于从选择器检索的值。最初,我传递了useEffect一个空的依赖项数组,因此它只会在挂载上添加事件侦听器。然而,显然这会导致选择器值周围的闭包过时。一个可行的解决方案是将选择器作为依赖项 \xe2\x80\x93 传递给它,但是,这会导致每次选择器的值更改时都会删除/重新添加侦听器,这不太好。

\n

我正在尝试考虑一种解决方案,该解决方案仅添加一次事件侦听器,同时允许被调用的方法访问选择器的当前值。

\n

示例代码:

\n
const currentValue = useSelector(state => getValue(state));\n\nuseEffect(() => {\n  document.addEventListener('my.custom.event', handleEvent);\n\n  return(() => document.removeEventListener('my.custom.event', handleEvent));\n}, []);\n\nconst handleEvent = () => {\n  console.log(currentValue)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

事实上,这会创建一个陈旧的闭包currentValue,以便在事件触发时,记录的值不一定是最新的。

\n

更改[][currentValue]inuseEffect会产生预期的行为,但会在每次更改 时删除/重新添加事件侦听器currentValue

\n

由于这不是组件的状态值,因此无法选择使用回调(例如console.log(currentValue => console.log(currentValue))访问最新值)。我还尝试使用 usinguseRef来保留该值,但我相信每次选择器的值发生变化时我都需要某种方法来更新 ref 值,这并不是一个解决方案。

\n

在实际组件中, 的值currentValue在 Redux 中被其他组件修改,因此将其更改为状态值也不太可行。

\n

我在想:

\n
    \n
  • 是否有办法在被调用的方法中刷新选择器的值;
  • \n
  • 唯一的解决方案是否是在依赖项更改时删除/重新添加侦听器,或者;
  • \n
  • 是否最好将此组件保留为类组件并完全绕过此问题(componentDidMount不会遇到过时的关闭问题。)
  • \n
\n

Ori*_*ori 6

useRef方法通常是此问题的解决方案,但您需要另一个方法useEffect来更新引用currentValue

const currentValue = useSelector(state => getValue(state));
const valueRef = useRef();

useEffect(() => { valueRef.current = currentValue; }, [currentValue]);

useEffect(() => {
  const handleEvent = () => {
    console.log(valueRef.current)
  }

  document.addEventListener('my.custom.event', handleEvent);

  return(() => document.removeEventListener('my.custom.event', handleEvent));
}, []);
Run Code Online (Sandbox Code Playgroud)

但是,您也可以从 中提取整个函数useEffect,并在钩子中使用它,这样您就可以轻松创建用于事件处理的自定义钩子:

const useEventHandler = (eventName, eventHandler, eventTarget = document) => {
  const eventHandlerRef = useRef();

  useEffect(() => {
    eventHandlerRef.current = eventHandler;
  });

  useEffect(() => {
    const handleEvent = (...args) => eventHandlerRef?.current(...args);

    eventTarget.addEventListener(eventName, handleEvent);

    return (() => eventTarget.removeEventListener(eventName, handleEvent));
  }, []);
}
Run Code Online (Sandbox Code Playgroud)

使用钩子:

const currentValue = useSelector(state => getValue(state));

useEventHandler('my.custom.event', () => console.log(currentValue))
Run Code Online (Sandbox Code Playgroud)