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

React Hooks实现点击其他菜单时关闭已展开的带子菜单的菜单

问题描述

我有一个包含菜单的侧边栏,部分菜单带有子菜单,希望实现点击不同菜单时,关闭所有已展开的带子菜单的菜单。

技术栈

React + React Hooks

当前代码

侧边栏组件

const [open, setOpen] = useState(true);

return (
  <div>
    <ul>
      {SidebarData.map((item, index) => {
        return (
          <SubMenu
            item={item}
            key={index}
            titleOpen={!open}
            dropOpen={open}
            subMenuOpen={open}
          />
        );
      })}
    </ul>
  </div>
);

子菜单组件

function SubMenu({ item, titleOpen, dropOpen, subMenuOpen }) {
  const [subnav, setSubnav] = useState(false);

  const showSubnav = () => setSubnav(!subnav);

  return (
    <div>
      <ul>
        <Link to={item.path} onClick={item.subNav && showSubnav}>
          <li>
            <span>{item.icon}</span>
            <span>
              {item.title}
            </span>
            {item.subNav && dropOpen && (
              <KeyboardArrowDownIcon/>
            )}
          </li>
        </Link>

        {subnav &&
          subMenuOpen &&
          item.subNav.map((item, index) => {
            return (
              <ul>
                <Link to={item.path} key={index}>
                  <li>
                    <div>{item.title}</div>
                  </li>
                </Link>
              </ul>
            );
          })}
      </ul>
    </div>
  );
}

注:每个菜单拥有唯一ID

解决方案

核心思路是把子菜单的展开状态从子组件提升到父组件统一管理,实现点击其他菜单时自动关闭已展开项。

1. 修改侧边栏组件状态

替换原有open状态,改为记录当前展开菜单的唯一ID:

const [expandedMenuId, setExpandedMenuId] = useState(null);

// 菜单点击处理逻辑
const handleMenuClick = (menuId) => {
  // 点击当前展开的菜单则关闭,否则展开当前菜单并关闭其他
  setExpandedMenuId(prevId => prevId === menuId ? null : menuId);
};

2. 传递状态与事件给子组件

渲染SubMenu时,传入当前展开状态和点击处理函数,同时用菜单唯一ID作为组件key:

{SidebarData.map((item) => {
  return (
    <SubMenu
      item={item}
      key={item.id}
      isExpanded={expandedMenuId === item.id}
      onMenuClick={() => handleMenuClick(item.id)}
      titleOpen={!open}
      dropOpen={open}
      subMenuOpen={open}
    />
  );
})}

3. 改造子菜单组件

移除子组件内部的subnav状态,改用父组件传递的isExpanded控制子菜单显示,点击事件委托给父组件处理:

function SubMenu({ item, titleOpen, dropOpen, subMenuOpen, isExpanded, onMenuClick }) {
  return (
    <div>
      <ul>
        <Link 
          to={item.path} 
          onClick={() => {
            if (item.subNav) {
              onMenuClick();
            }
          }}
        >
          <li>
            <span>{item.icon}</span>
            <span>{item.title}</span>
            {item.subNav && dropOpen && (
              <KeyboardArrowDownIcon/>
            )}
          </li>
        </Link>

        {/* 用父组件传递的isExpanded替代原subnav状态 */}
        {isExpanded &&
          subMenuOpen &&
          item.subNav.map((subItem, index) => {
            return (
              <ul key={index}>
                <Link to={subItem.path}>
                  <li>
                    <div>{subItem.title}</div>
                  </li>
                </Link>
              </ul>
            );
          })}
      </ul>
    </div>
  );
}

关键说明

  • 状态提升:将子菜单展开状态统一由父组件管理,确保所有子菜单的展开状态同步,实现点击其他菜单时自动关闭已展开项。
  • 唯一ID复用:用菜单唯一ID作为组件key,避免index导致的组件更新异常,同时方便匹配当前展开的菜单。
  • 点击逻辑解耦:子菜单仅负责触发点击事件,状态更新逻辑交给父组件,降低组件耦合度。

内容的提问来源于stack exchange,提问作者King

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:06:42