我如何 window removeEventListener 使用 React useEffect

How do I window removeEventListener using React useEffect

在 React Hooks 文档中展示了如何在组件的清理阶段移除 EventListener。 https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

在我的用例中,我尝试以功能组件的状态 属性 为条件移除 EventListener。

这是一个永远不会卸载组件但应该删除事件侦听器的示例:

function App () {
  const [collapsed, setCollapsed] = React.useState(true);

  React.useEffect(
    () => {
      if (collapsed) {
        window.removeEventListener('keyup', handleKeyUp); // Not the same "handleKeyUp" :(
      } else {
        window.addEventListener('keyup', handleKeyUp);
      }
    },
    [collapsed]
  );

  function handleKeyUp(event) {
    console.log(event.key);
    switch (event.key) {
      case 'Escape':
        setCollapsed(true);
        break;
    }
  }

  return collapsed ? (
    <a href="javascript:;" onClick={()=>setCollapsed(false)}>Search</a>
  ) : (
    <span>
      <input placeholder="Search" autoFocus />&nbsp;
      <a href="javascript:;">This</a>&nbsp;
      <a href="javascript:;">That</a>&nbsp;
      <input placeholder="Refinement" />
    </span>
  );
}
ReactDOM.render(<App />, document.body.appendChild(document.createElement('div')));

(现场样本在 https://codepen.io/caqu/pen/xBeBMN

我看到的问题是 removeEventListener 中的 handleKeyUp 引用在每次组件呈现时都会发生变化。函数 handleKeyUp 需要对 setCollapsed 的引用,因此它必须包含在 App 中。将 handleKeyUp 移入 useEffect 似乎也会触发多次并丢失对原始 handleKeyUp.

的引用

如何在不卸载组件的情况下有条件地 window.removeEventListener 使用 React Hooks?

您可以将 handleKeyUp 函数放在给定 useEffect (which is the recommended way of doing it according to the official documentation) 的函数中,并且只添加侦听器和 return 当 collapsed 是错误的。

useEffect(() => {
  if (collapsed) {
    return;
  }

  function handleKeyUp(event) {
    switch (event.key) {
      case "Escape":
        setCollapsed(true);
        break;
    }
  }

  window.addEventListener("keyup", handleKeyUp);
  return () => window.removeEventListener("keyup", handleKeyUp);
}, [collapsed]);

Tholle 的 答案可能有效,但在 if.

中声明函数是不好的做法

函数声明和未声明的时间更难理解。它还可能导致错误,因为函数被提升了。

有更简洁的方法来修复它:

通过使用 useCallback 挂钩包装您的事件处理程序。

const [collapsed, setCollapsed] = useState(true)

const handleKeyUp = useCallback((event) => {
    if (event.key === "Escape") {
      setCollapsed(true)
    }
}, [setCollapsed])

useEffect(() => {
    if (!collapsed) {
        window.addEventListener("keyup", handleKeyUp)
    } else {
        window.removeEventListener("keyup", handleKeyUp)
    }

    return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed, handleKeyUp])
  • useCallback 依赖于 setCollapsed。这确保 handleKeyUp 在组件重新呈现时不会被重新定义(这总是在状态更改时发生)
  • useEffect 将有条件地 add/remove 事件侦听器,否则只要安装了组件,事件就会一直触发。

If you use a lot of event handlers in useEffect, there's a custom hook for that: https://usehooks.com/useEventListener/

这是使用我的解决方案更新的问题海报示例:https://codepen.io/publicJorn/pen/eYzwENN