React嵌套菜单Hover状态异常:子级Nav.Item无法添加selected类
解决方案
要实现你需要的嵌套菜单hover选中逻辑,核心问题是原来的单个selectedItemId无法同时维护父项和子项的选中状态,我们需要改用集合来存储所有需要保持选中的项ID,并在hover时处理同级项的移除和父链的添加。以下是修改后的完整代码:
"use client"; import Nav from 'react-bootstrap/Nav'; import Link from 'next/link'; import { getMenu } from '@/lib/APIs/menu'; import { use, useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; const dataPromise = getMenu(); const GlobalNav = () => { const MENU = use(dataPromise); const pathname = usePathname(); // 改用Set存储所有需要选中的项ID const [selectedItemIds, setSelectedItemIds] = useState<Set<string>>(new Set()); // 处理hover逻辑:移除同级项的选中状态,添加当前项及所有父项 function handleItemHover(itemId: string, siblingIds: string[], parentChain: string[]) { setSelectedItemIds(prev => { const newSet = new Set(prev); // 移除当前项的所有同级项的选中状态 siblingIds.forEach(id => newSet.delete(id)); // 添加当前项和它的所有父项 newSet.add(itemId); parentChain.forEach(id => newSet.add(id)); return newSet; }); } // 递归渲染菜单,传递父项ID链和同级项ID数组 function renderMenuItems(menuItems: any[], parentChain: string[] = []) { const siblingIds = menuItems.map(item => item.id); return menuItems.map((item: any) => { const currentChain = [...parentChain, item.id]; return ( <Nav.Item as='li' key={item.id} onMouseOver={() => handleItemHover(item.id, siblingIds, parentChain)} className={selectedItemIds.has(item.id) ? 'selected' : ''} > <Nav.Link as={Link} href={item.url} > <span> {item.title} </span> </Nav.Link> {item.children && item.children.length > 0 && ( <Nav as='ul'> {renderMenuItems(item.children, currentChain)} </Nav> )} </Nav.Item> ); }); } // 初始加载时,根据当前路由设置选中项及父项 useEffect(() => { const findActiveItemChain = (items: any[], chain: string[] = []): string[] | null => { for (const item of items) { if (item.url === pathname) { return [...chain, item.id]; } if (item.children?.length) { const result = findActiveItemChain(item.children, [...chain, item.id]); if (result) return result; } } return null; }; const activeChain = findActiveItemChain(MENU); if (activeChain) { setSelectedItemIds(new Set(activeChain)); } }, [pathname, MENU]); return ( <Nav as='ul'> {renderMenuItems(MENU)} </Nav> ); } export default GlobalNav;
关键修改说明:
- 状态存储方式变更:将单个
selectedItemId改为Set<string>类型的selectedItemIds,支持同时标记多个项(当前hover项+所有父项)为选中状态。 - hover逻辑优化:
- 每次hover时,先移除当前项所有同级项的选中状态,满足"仅hover同级其他项时才移除当前项selected类"的需求。
- 添加当前项及其所有父项到选中集合,保证子菜单显示时父项保持选中。
- 递归渲染传参:
renderMenuItems新增parentChain参数传递父项ID链,同时生成当前菜单组的siblingIds(同级项ID数组),供hover逻辑使用。 - 初始路由匹配:通过
useEffect结合usePathname,初始化时自动将当前路由对应的菜单项及其所有父项标记为选中,提升用户体验。
这样修改后,就能完全满足你提出的4条需求:
- 任何
Nav.Item被hover时都会添加selected类 - 鼠标离开项时不会移除
selected类 - 显示子菜单时,父级
Nav.Item保持selected类 - 仅当hover同级其他
Nav.Item时,才移除当前项的selected类
内容的提问来源于stack exchange,提问作者levipadre
相关产品推荐
相关产品推荐

