【问题标题】:How to properly clean up open menu when navigating to next page导航到下一页时如何正确清理打开的菜单
【发布时间】:2021-01-04 01:03:08
【问题描述】:

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

单击“打开菜单”按钮时,应用程序显示包含菜单的div,并将 eventListener 添加到文档单击事件中。当添加 eventListener 并且用户单击文档中的任意位置时,函数会检查菜单内是否发生了单击,如果是,则不执行任何操作。如果点击在菜单之外,它会移除 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;

【问题讨论】:

    标签: javascript reactjs typescript use-effect


    【解决方案1】:

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

    试试:

    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;

    【讨论】:

    • 当我向按钮元素添加另一个引用时,此解决方案开始起作用,并且在 closeMenu-function 中,我添加了一个检查按钮元素内部是否发生了点击。否则,在单击该按钮时菜单在打开之前已关闭。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-12
    • 1970-01-01
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    相关资源
    最近更新 更多