导航至下一页时如何正确清理打开的菜单

问题描述

我创建了简单但有效的下拉菜单。在此示例中,菜单仅包含文本“菜单”且不包含样式。

单击“打开菜单”按钮时,应用程序将显示包含菜单的div,并将eventListener添加到文档单击事件中。添加eventListener并用户单击文档中的任意位置时,函数将检查菜单内是否发生了单击,如果是,则不执行任何操作。如果click在菜单外,它将删除eventHandler并关闭菜单。

此方法有什么问题吗?这样做的主要问题是,当我单击页面上的任何链接时如果打开菜单,则会收到此讨厌的反应警告:

index.js:1警告:无法在已卸载的组件上执行React状态更新。这是空操作,但它表明应用程序中发生内存泄漏。要修复,请取消使用useEffect清理功能中的所有订阅和异步任务。

我已经添加了useEffect清理功能,以便在打开菜单时删除eventHandler,但这无济于事,我仍然收到相同的错误消息。

你能指出我做错了什么吗?

const DropDown = () => {
  const [open,setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  const closeMenu = (event: MouseEvent) => {
    if (event.target && event.target instanceof HTMLElement && ref.current) {
      if (ref.current.contains(event.target)) return;
    }

    document.removeEventListener('click',closeMenu);
    setOpen(false);
  };

  const toggleMenu = () => {
    if (!open) {
      document.addEventListener('click',closeMenu);
      setOpen(true);
    } else {
      document.removeEventListener('click',closeMenu);
      setOpen(false);
    }
  };

  useEffect(() => {
    return () => {
      if (open) {
        document.removeEventListener('click',closeMenu);
        setOpen(false);
      }
    };
  },[closeMenu]);

  return (
    <>
      <button onClick={toggleMenu}>open menu</button>
      {open && <div ref={ref}>menu</div>}
    </>
  );
};

export default DropDown;

解决方法

我认为问题是由setOpen(false);钩中的useEffect引起的:卸载组件时会调用清除函数,因此设置状态是没有意义的。出于同样的原因,检查菜单是否打开是多余的。如果菜单已卸载,则可以删除事件处理程序。

尝试:

const DropDown = () => {
    const [open,setOpen] = useState(false);
    const ref = useRef < HTMLDivElement > (null);

    /*
     *"useCallback()" hook will avoid unnecessary re-creating
     * the function at each re-render
     */
    const closeMenu = useCallback((event: MouseEvent) => {
      if (event.target && event.target instanceof HTMLElement && ref.current) {
        if (ref.current.contains(event.target)) return;
      }

      /* 
       * You do not need to remove the event handler here
       * you create it when the component is mounted and
       * remove it when it is unmounted via the "useEffect()" hook
       */
      //document.removeEventListener('click',closeMenu);
      setOpen(false);
    },[]);

    const toggleMenu = () => {
      /*
       * This is not necessary,you can do it in one line.
       * Actually,you can entirely remove the function and
       * call 'setOpen(!open)' directly from the button:
       * <button onClick={() => {setOpen(!open)}}>
       */
      //if (!open) {
      //  document.addEventListener('click',closeMenu);
      //  setOpen(true);
      //} else {
      //  document.removeEventListener('click',closeMenu);
      //  setOpen(false);
      //}
      setOpen(!open);
    };

    useEffect(() => {
      document.addEventListener('click',closeMenu);
      return () => {
        document.removeEventListener('click',closeMenu);
      };
    },[closeMenu]);

    return ( <
      >
      <
      button onClick = {
        toggleMenu
      } > open menu < /button> {
        open && < div ref = {
            ref
          } > menu < /div>} <
          />
      );
    };

    export default DropDown;

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...