div绑定id/ref时document.activeElement判断失效 点击菜单外关闭功能异常
核心问题分析
- 问题1:普通div元素默认无法获得焦点
你用来包裹菜单的<div>默认不支持聚焦,哪怕手动调用focus()方法也无法让它成为document.activeElement,所以document.activeElement !== menuRef.current的判断永远为真。如果要让div可聚焦,需要给它添加tabIndex属性,比如tabIndex="-1"(表示只能通过js调用focus聚焦,不能通过tab键切换聚焦)。 - 问题2:定时器id存储方式错误
你在组件内部直接声明let closeTimeMenu存储定时器id,组件每次重新渲染时这个变量都会被重置,之前存的定时器id会丢失,导致clearTimeout根本无法正常清除定时器。需要用useRef来存储定时器id,useRef的引用值会跨渲染周期保持稳定。 - 问题3:点击区域判断逻辑不合理
用activeElement判断是否点击了菜单内部本身可靠性极低,更合理的方案是直接判断点击事件的触发节点是否被菜单容器包含,调用DOM元素的contains()方法即可实现。 - 问题4:事件绑定与回调逻辑错误
你代码中window.onclick = toggleMenu(false)的写法是错误的:toggleMenu(false)会被立即执行,而不是作为点击事件的回调触发,且不需要额外给window绑定点击事件,监听到外部点击后直接调用toggleMenu(false)即可。 - 问题5:闭包与依赖遗漏问题
你的useEffect依赖数组为空,但内部用到了toggleMenu方法,存在闭包陷阱,可能导致获取到的永远是初始渲染时的旧方法引用。
修正后的代码示例
import { useRef, useEffect } from 'react'; import { useMenuState, ControlledMenu, MenuItem } from '@szhsin/react-menu'; export default function Example() { const { toggleMenu } = useMenuState({ transition: true }); // 用useRef存定时器id,跨渲染保持稳定 const closeTimeMenu = useRef(null); const menuRef = useRef(null); useEffect(() => { function handleClickOutside(e) { // 直接判断点击目标是否在菜单容器内 if (menuRef.current && !menuRef.current.contains(e.target)) { setTimeout(() => { // 清除定时器 if (closeTimeMenu.current) { clearTimeout(closeTimeMenu.current); } toggleMenu(false); }, 500); } } document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [toggleMenu]); // 把toggleMenu加入依赖数组 function timeout() { closeTimeMenu.current = setTimeout(function () { toggleMenu(false); }, 1500); } return ( {/* 给div加tabIndex属性,支持聚焦(如果后续还有聚焦需求的话) */} <div ref={menuRef} id="myField" tabIndex="-1"> <button onMouseEnter={() => { toggleMenu(true); if (closeTimeMenu.current) { clearTimeout(closeTimeMenu.current); } }} onMouseLeave={() => timeout()} > hover打开菜单 </button> <ControlledMenu anchorRef={menuRef} onMouseEnter={() => { toggleMenu(true); if (closeTimeMenu.current) { clearTimeout(closeTimeMenu.current); } }} onItemClick={() => { toggleMenu(false); }} onMouseLeave={() => timeout()} > <MenuItem>保存</MenuItem> <MenuItem>关闭窗口</MenuItem> </ControlledMenu> </div> ); }
内容的提问来源于stack exchange,提问作者Elassoa
相关产品推荐
相关产品推荐

