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

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; 

关键修改说明:

  1. 状态存储方式变更:将单个selectedItemId改为Set<string>类型的selectedItemIds,支持同时标记多个项(当前hover项+所有父项)为选中状态。
  2. hover逻辑优化:
    • 每次hover时,先移除当前项所有同级项的选中状态,满足"仅hover同级其他项时才移除当前项selected类"的需求。
    • 添加当前项及其所有父项到选中集合,保证子菜单显示时父项保持选中。
  3. 递归渲染传参:renderMenuItems新增parentChain参数传递父项ID链,同时生成当前菜单组的siblingIds(同级项ID数组),供hover逻辑使用。
  4. 初始路由匹配:通过useEffect结合usePathname,初始化时自动将当前路由对应的菜单项及其所有父项标记为选中,提升用户体验。

这样修改后,就能完全满足你提出的4条需求:

  • 任何Nav.Item被hover时都会添加selected类
  • 鼠标离开项时不会移除selected类
  • 显示子菜单时,父级Nav.Item保持selected类
  • 仅当hover同级其他Nav.Item时,才移除当前项的selected类

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 12:35:06