React自定义上下文菜单问题:打开新菜单时旧菜单未关闭
解决多组件实例下自定义上下文菜单不关闭旧菜单的问题
问题核心在于每个AlbumCard组件都独立维护自己的isShow状态,右键新卡片时,旧卡片的菜单状态不会被更新,导致旧菜单残留。下面提供两种可行的解决思路:
方案一:状态提升到父组件(推荐)
将菜单的显示状态、位置统一放到父组件管理,所有子卡片只负责触发右键事件,这样就能保证同一时间只有一个菜单显示。
修改后的代码示例:
// 父组件:统一管理菜单状态 function AlbumList({ albums }) { const [menuState, setMenuState] = useState({ isShow: false, position: { x: 0, y: 0 }, targetAlbum: null // 可选:记录触发菜单的卡片数据 }); // 点击外部/失焦时关闭菜单 useEffect(() => { const handleCloseMenu = () => setMenuState(prev => ({ ...prev, isShow: false })); const events = ["click", "blur"]; events.forEach(e => window.addEventListener(e, handleCloseMenu)); return () => { events.forEach(e => window.removeEventListener(e, handleCloseMenu)); }; }, []); // 监听全局右键事件,先关闭现有菜单 useEffect(() => { const handleGlobalContext = () => { if (menuState.isShow) setMenuState(prev => ({ ...prev, isShow: false })); }; window.addEventListener("contextmenu", handleGlobalContext); return () => window.removeEventListener("contextmenu", handleGlobalContext); }, [menuState.isShow]); // 处理卡片右键事件 const handleCardRightClick = (e, album) => { e.preventDefault(); setMenuState({ isShow: true, position: { x: e.pageX, y: e.pageY }, targetAlbum: album }); }; return ( <div className="album-list"> {albums.map((album, index) => ( <AlbumCard key={index} index={index} album={album} onContextMenu={(e) => handleCardRightClick(e, album)} /> ))} {/* 全局唯一的菜单Portal */} {menuState.isShow && ( <ContextMenu position={menuState.position} targetAlbum={menuState.targetAlbum} /> )} </div> ); } // 子组件:移除独立的菜单状态逻辑 function AlbumCard({ index, album, onContextMenu }) { return ( <div className="album-card" onContextMenu={onContextMenu}> {/* 卡片内容 */} </div> ); }
方案二:全局自定义事件通知(快速改造)
如果不想调整组件层级,可以通过自定义全局事件,让新菜单打开时通知所有旧菜单关闭。
修改后的useContextMenu Hook:
function useContextMenu() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [isShow, setIsShow] = useState(false); useEffect(() => { // 点击外部/失焦关闭菜单 const handleClose = () => setIsShow(false); // 收到其他菜单打开的通知时关闭当前菜单 const handleCloseFromOther = () => setIsShow(false); // 右键其他区域时关闭当前菜单 const handleGlobalRightClick = () => setIsShow(false); const basicEvents = ["click", "blur"]; basicEvents.forEach(e => window.addEventListener(e, handleClose)); window.addEventListener("contextmenu", handleGlobalRightClick); window.addEventListener("close-all-context-menus", handleCloseFromOther); return () => { basicEvents.forEach(e => window.removeEventListener(e, handleClose)); window.removeEventListener("contextmenu", handleGlobalRightClick); window.removeEventListener("close-all-context-menus", handleCloseFromOther); }; }, []); function onClick(event) { event.preventDefault(); // 触发全局事件,通知所有其他菜单关闭 window.dispatchEvent(new CustomEvent("close-all-context-menus")); setPosition({ x: event.pageX, y: event.pageY }); setIsShow(true); } function contextMenu() { return isShow && createPortal(<ContextMenu position={position} />, document.body); } return [contextMenu, onClick]; }
额外修复:原代码拼写错误
注意原Hook中卸载逻辑里的hideEvnet是拼写错误,应该改为hideEvent,否则移除事件监听会失效。
内容的提问来源于stack exchange,提问作者Nguyen Ngoc
相关产品推荐
相关产品推荐

