React 嵌套菜单上的悬停状态

Hover states on React nested menu

提问人:levipadre 提问时间:7/13/2023 最后编辑:levipadre 更新时间:7/13/2023 访问量:68

问:

在下面的代码中,我有一个嵌套菜单。悬停时,我想将类名添加到 Nav.Item 中,并且仅在将鼠标悬停在另一个 .我可以用.不幸的是,它不会将类添加到任何子级别。selectedNav.ItemonMouseOverselectedNav.Item

因此,标准是: 对于将鼠标悬停在任何 Nav.Item 上,应添加选定的类

  • 如果鼠标离开项目,则不删除类selected
  • 将类保留在“出现子菜单时”上selectedNav.Item
  • 仅删除类,如果将鼠标悬停在另一个类上但在同一级别上selectedNav.Item
"use client"; 
 
import Nav from 'react-bootstrap/Nav'; 
import Link from 'next/link'; 
 
import { getMenu } from '@/lib/APIs/menu'; 
import { use, useEffect, useRef, useState } from 'react'; 
import { usePathname } from 'next/navigation'; 
import { Button, NavDropdown } from 'react-bootstrap'; 
 
const dataPromise = getMenu(); 
 
const GlobalNav = () => { 
    const MENU = use(dataPromise); 
    const [selectedItemId, setSelectedItemId] = useState<string | null>(null); 
 
    function handleItemHover(itemId: string) { 
        setSelectedItemId(itemId); 
    } 
 
    function renderMenuItems(menuItems: any[]) { 
        return menuItems.map((item: any) => ( 
            <Nav.Item 
                as='li' 
                key={item.id}    
                onMouseOver={() => handleItemHover(item.id)} 
                className={selectedItemId === 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)} 
                    </Nav> 
                )} 
            </Nav.Item> 
        )); 
    } 
 
    return ( 
        <Nav as='ul'> 
            {renderMenuItems(MENU)} 
        </Nav> 
    ); 
} 
 
export default GlobalNav; 

我希望有人能帮忙。

JavaScript ReactJS TypeScript Next.js 事件冒泡

评论


答:

1赞 Emil J 7/13/2023 #1

试试这个:

function hasSelectedChild(menuItem) {
   return menuItem.children && menuItem.children.length > 0 && menuItem.children.find(item => item.id === selectedItemId || hasSelectedChild(item)) 
}

function renderMenuItems(menuItems: any[]) { 
        return menuItems.map((item: any) => ( 
            <Nav.Item 
                as='li' 
                key={item.id}    
                onMouseOver={(e) => {
                  e.stopPropagation();
                  handleItemHover(item.id)
                }} 
                className={(hasSelectedChild(item) || selectedItemId === 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)} 
                    </Nav> 
                )} 
            </Nav.Item> 
        )); 
    }

评论

0赞 levipadre 7/13/2023
谢谢 @Emil J,它几乎是完美的,但现在当我将鼠标悬停在第一级上时,它会为第二级、第三级等的所有 s 添加类,而不仅仅是我悬停在上面的那个。Nav.ItemselectedNav.Item
0赞 Emil J 7/13/2023
啊,好的,那么我会写一个函数来检查一个项目是否有一个选定的子项。像这样递归的东西:并使用它来设置 className,如下所示:function hasSelectedChild(menuItem) { return menuItems.children.length > 0 && menuItems.children.find(item => item.id === selectedItemId || hasSelectedChild(item)) }className={hasSelectedChild(item) || selectedItemId === item.id ? 'selected' : ''}
0赞 Emil J 7/13/2023
我现在使用这种方法更新了我的答案
1赞 Emil J 7/13/2023
现在,我再次编辑了我的答案,以阻止 mouseOver 事件传播到父导航项。测试过它,似乎可以:)
1赞 levipadre 7/13/2023
它确实有效!非常感谢你@Emil J,你是救命恩人。
1赞 KGS 7/13/2023 #2

若要实现在悬停时将“selected”类添加到 Nav.Item 元素并在出现子菜单时保留该类的预期行为,可以按如下方式修改代码:

import Nav from 'react-bootstrap/Nav';
import Link from 'next/link';
import { getMenu } from '@/lib/APIs/menu';
import { usePathname } from 'next/navigation';
import { Button, NavDropdown } from 'react-bootstrap';

const dataPromise = getMenu();

const GlobalNav = () => {
  const MENU = use(dataPromise);
  const [selectedItemId, setSelectedItemId] = useState<string | null>(null);

  function handleItemHover(itemId: string) {
    setSelectedItemId(itemId);
  }

  function handleItemMouseLeave() {
    setSelectedItemId(null);
  }

  function renderMenuItems(menuItems: any[]) {
    return menuItems.map((item: any) => (
      <Nav.Item
        as='li'
        key={item.id}
        onMouseOver={() => handleItemHover(item.id)}
        onMouseLeave={handleItemMouseLeave}
        className={selectedItemId === 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' className={selectedItemId === item.id ? 'selected' : ''}>
            {renderMenuItems(item.children)}
          </Nav>
        )}
      </Nav.Item>
    ));
  }

  return (
    <Nav as='ul'>
      {renderMenuItems(MENU)}
    </Nav>
  );
}

export default GlobalNav;

将 className 属性添加到嵌套的 Nav 组件中,以在选择父 Nav.Item 时添加“selected”类。并将 onMouseLeave={handleItemMouseLeave} 事件处理程序添加到 Nav.Item 以触发 selectedItemId 的重置