You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:42:47