如何使用 React useEffect 窗口 removeEventListener

IT技术 reactjs react-hooks removeeventlistener
2021-04-06 18:07:58

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

在我的用例中,我试图将 removeEventListener 以功能组件的状态属性作为条件。

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

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 上的实时示例

我看到的问题是,每次组件渲染时,handleKeyUp内部引用removeEventListener都会发生变化。该函数handleKeyUp需要引用,setCollapsed因此必须用 括起来ApphandleKeyUp在里面移动useEffect似乎也多次触发并失去对原始的引用handleKeyUp

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

2个回答

您可以将handleKeyUp函数放在给定的函数中useEffect根据官方文档,这是推荐的做法),并且仅在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]);

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虽然有额外的右花括号
2021-05-23 18:07:58
为什么把setCollapsed当作一个论点useCallback难道它不等于只是做[]吗?
2021-05-25 18:07:58
如果可能的话,实际上 Tholle 的回答更好。只要可能,最好在 useEffect 中定义回调。那么你就不需要为 useCallback 烦恼了。正如 Kent C Dodds 所建议的:“如果你必须定义一个函数来调用你的效果,那么就在效果回调内部而不是外部进行。” Epireact.dev/myths-about-useeffect
2021-05-25 18:07:58
这正是我一直在寻找的,非常感谢@publicJorn!
2021-05-29 18:07:58
event上面例子中的定义什么@publicJorn
2021-06-12 18:07:58