React Bootstrap嵌套菜单激活项默认不展开问题求助
解决React Bootstrap嵌套菜单默认展开激活页面父菜单的问题
你的核心需求是让当前激活页面对应的所有父折叠菜单默认展开,下面是具体的修改方案:
1. 修复子菜单激活检查逻辑(支持多层嵌套)
原hasActiveChild仅检查直接子项,多层嵌套场景下会失效,改成递归检查所有后代路由:
const hasActiveChild = (currentItem) => { // 检查当前项的直接子路由是否匹配 const hasDirectActiveChild = currentItem.child.some(child => child.url === currentRoute); // 递归检查子项的后代路由是否匹配 const hasNestedActiveChild = currentItem.child.some(child => child.child && hasActiveChild(child)); return hasDirectActiveChild || hasNestedActiveChild; };
2. 初始化展开状态,页面加载时直接展开需要的菜单
把open的初始状态从全false,改成根据菜单是否包含激活子项自动设置:
// 初始状态:包含激活子项的父菜单默认展开 const initialOpenState = menu.map(item => item.child && hasActiveChild(item)); const [open, setOpen] = useState(initialOpenState);
3. 修改useEffect,路由或菜单变化时自动更新展开状态
原useEffect仅处理菜单长度变化,现在改成监听路由和菜单数据变化,自动调整展开状态的同时保留用户手动操作的状态:
useEffect(() => { setOpen(prevOpen => prevOpen.map((isOpen, index) => { const menuItem = menu[index]; // 仅当当前是父菜单且包含激活子项时强制展开,其他情况保留用户手动设置的状态 return menuItem.child && hasActiveChild(menuItem) ? true : isOpen; })); }, [currentRoute, menu]);
完整修改后的renderMenu代码
const renderMenu = (menu) => { const router = useRouter(); const currentRoute = router.pathname; const hasActiveChild = (currentItem) => { const hasDirectActiveChild = currentItem.child.some(child => child.url === currentRoute); const hasNestedActiveChild = currentItem.child.some(child => child.child && hasActiveChild(child)); return hasDirectActiveChild || hasNestedActiveChild; }; // 初始状态:包含激活子项的父菜单默认展开 const initialOpenState = menu.map(item => item.child && hasActiveChild(item)); const [open, setOpen] = useState(initialOpenState); useEffect(() => { setOpen(prevOpen => prevOpen.map((isOpen, index) => { const menuItem = menu[index]; return menuItem.child && hasActiveChild(menuItem) ? true : isOpen; })); }, [currentRoute, menu]); return menu.map((item, index) => ( <Nav.Item className={item.child && "dropdown"} key={index}> {item.url ? <Nav.Link href={item.url} className={(currentRoute === item.url) && "active"}> <span>{item.title}</span> </Nav.Link> : <Nav.Link className={hasActiveChild(item) && "active"} onClick={() => { setOpen((open) => open.map((o, i) => { if (i === index) return !o; return o; })); }} aria-controls={`collapse${capitalizeEachWord(item.title)}`} aria-expanded={open[index]} data-bs-toggle="collapse"> <span>{item.title}</span> </Nav.Link> } {item.child && ( <Collapse in={open[index]} key={index} data-open={open[index]}> <div id={`collapse${capitalizeEachWord(item.title)}`}> <div className="nav flex-column"> {renderMenu(item.child)} </div> </div> </Collapse> )} </Nav.Item> )) };
关键说明
- 递归检查:确保多层嵌套的子页面激活时,所有层级的父菜单都能自动展开
- 状态保留:用户手动折叠/展开菜单后,非激活路径的菜单状态会被保留,仅激活路径的父菜单会强制展开
- 依赖优化:useEffect仅监听
currentRoute和menu变化,避免不必要的重渲染和无限循环
内容的提问来源于stack exchange,提问作者levipadre
相关产品推荐
相关产品推荐

