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

如何为React菜单的子菜单项动态添加open状态?

解决方案

问题根源

你遇到的报错是因为违反了React Hook的规则:useState必须在函数组件的顶层调用,不能放在useEffect、循环或条件判断这类嵌套逻辑里。你之前尝试在useEffect里给菜单项动态添加状态,本质是在副作用回调里调用Hook,这是不被允许的。

方案一:集中管理子菜单展开状态

在侧边栏组件内维护一个状态对象,记录所有带子菜单的菜单项的展开状态,不需要修改/utils/Menus.js里的原始菜单结构(只需给每个子菜单加唯一标识id)。

1. 完善菜单定义(添加唯一ID)

// /utils/Menus.js
export const adminMenu = [
  { title: "仪表盘", path: "/dashboard", type: "link" },
  {
    title: "用户管理",
    id: "user-management", // 唯一标识,用于绑定状态
    type: "submenu",
    subitems: [
      { title: "用户列表", path: "/users/list" },
      { title: "新增用户", path: "/users/add" }
    ]
  }
];

export const regularUserMenu = [
  { title: "个人中心", path: "/profile", type: "link" },
  {
    title: "我的订单",
    id: "my-orders",
    type: "submenu",
    subitems: [
      { title: "待发货", path: "/orders/pending" },
      { title: "已完成", path: "/orders/completed" }
    ]
  }
];

2. 侧边栏组件实现

// /Sidenav.js
import { useState } from 'react';
import { Collapse, Button } from 'react-bootstrap';
import { adminMenu, regularUserMenu } from './utils/Menus';

function Sidenav({ userType }) {
  // 根据用户类型获取对应菜单
  const currentMenu = userType === 'admin' ? adminMenu : regularUserMenu;
  // 集中管理子菜单展开状态:key为菜单项id,value为布尔值
  const [submenuStates, setSubmenuStates] = useState({});

  // 切换指定子菜单的展开状态
  const toggleSubmenu = (menuId) => {
    setSubmenuStates(prev => ({
      ...prev,
      [menuId]: !prev[menuId]
    }));
  };

  // 渲染单个菜单项
  const renderMenuItem = (item) => {
    if (item.type === 'link') {
      return (
        <Button key={item.path} variant="link" href={item.path} className="w-100 text-start">
          {item.title}
        </Button>
      );
    }

    if (item.type === 'submenu') {
      const isOpen = submenuStates[item.id] || false;
      return (
        <div key={item.id} className="mb-2">
          <Button
            onClick={() => toggleSubmenu(item.id)}
            variant="light"
            className="w-100 text-start d-flex justify-content-between align-items-center"
          >
            {item.title}
            <span>{isOpen ? '▼' : '▶'}</span>
          </Button>
          <Collapse in={isOpen}>
            <div className="ms-4 mt-1">
              {item.subitems.map(subitem => (
                <Button key={subitem.path} variant="link" href={subitem.path} className="w-100 text-start">
                  {subitem.title}
                </Button>
              ))}
            </div>
          </Collapse>
        </div>
      );
    }

    return null;
  };

  return (
    <div className="sidenav p-3 border-end">
      {currentMenu.map(renderMenuItem)}
    </div>
  );
}

export default Sidenav;

方案二:独立子菜单组件(解耦状态管理)

创建专门的Submenu组件,让每个子菜单自己维护展开状态,侧边栏组件只需负责渲染菜单结构,无需关心状态逻辑。

1. 子菜单组件

// /components/Submenu.js
import { useState } from 'react';
import { Collapse, Button } from 'react-bootstrap';

function Submenu({ title, subitems }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="mb-2">
      <Button
        onClick={() => setIsOpen(!isOpen)}
        variant="light"
        className="w-100 text-start d-flex justify-content-between align-items-center"
      >
        {title}
        <span>{isOpen ? '▼' : '▶'}</span>
      </Button>
      <Collapse in={isOpen}>
        <div className="ms-4 mt-1">
          {subitems.map(subitem => (
            <Button key={subitem.path} variant="link" href={subitem.path} className="w-100 text-start">
              {subitem.title}
            </Button>
          ))}
        </div>
      </Collapse>
    </div>
  );
}

export default Submenu;

2. 侧边栏组件简化版

// /Sidenav.js
import { Button } from 'react-bootstrap';
import { adminMenu, regularUserMenu } from './utils/Menus';
import Submenu from './components/Submenu';

function Sidenav({ userType }) {
  const currentMenu = userType === 'admin' ? adminMenu : regularUserMenu;

  const renderMenuItem = (item) => {
    if (item.type === 'link') {
      return (
        <Button key={item.path} variant="link" href={item.path} className="w-100 text-start mb-2">
          {item.title}
        </Button>
      );
    }

    if (item.type === 'submenu') {
      return <Submenu key={item.title} title={item.title} subitems={item.subitems} />;
    }

    return null;
  };

  return (
    <div className="sidenav p-3 border-end">
      {currentMenu.map(renderMenuItem)}
    </div>
  );
}

export default Sidenav;

方案对比

  • 方案一:适合需要全局控制子菜单状态的场景(比如添加“折叠所有”按钮),状态集中管理,便于统一操作。
  • 方案二:代码解耦度更高,每个子菜单独立维护状态,侧边栏逻辑更简洁,适合大多数常规场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:13:14