【问题标题】:How do I window removeEventListener using React useEffect如何使用 React useEffect 窗口 removeEventListener
【发布时间】:2019-08-17 01:09:21
【问题描述】:

在 React Hooks 文档中,展示了如何在组件的清理阶段移除事件监听器。 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 的引用。

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

【问题讨论】:

标签: reactjs react-hooks removeeventlistener


【解决方案1】:

您可以将handleKeyUp 函数放在赋予useEffect (which is the recommended way of doing it according to the official documentation) 的函数中,并且仅在collapsed 为false 时添加侦听器并返回清理函数。

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

【讨论】:

    【解决方案2】:

    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 将有条件地添加/删除事件监听器,否则只要组件被挂载,事件就会一直触发。

    如果您在 useEffect 中使用大量事件处理程序,则有一个自定义挂钩:https://usehooks.com/useEventListener/

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

    【讨论】:

    • 非常有帮助!不过,您的 useEffect 中似乎有额外的右花括号。
    • 这正是我想要的,非常感谢@publicJorn!
    • 为什么将setCollapsed 作为useCallback 的参数?不就相当于只做[]吗?
    • 上例中event的定义是什么? @publicJorn
    • 实际上,如果可能的话,Tholle 的答案会更好。尽可能在 useEffect 中定义回调。那么你就不需要为 useCallback 操心了。正如 Kent C Dodds 所建议的那样:“如果你必须定义一个函数来调用你的效果,那么就在效果回调内部进行,而不是在外部进行。” epicreact.dev/myths-about-useeffect
    猜你喜欢
    • 2022-07-15
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2021-03-08
    • 2021-05-16
    • 2020-09-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多