如何在递归渲染的 React 多级菜单中精准控制按钮点击行为

17次阅读

如何在递归渲染的 React 多级菜单中精准控制按钮点击行为

本文介绍如何基于菜单项数据结构(而非递归组件状态)判断是否可点击,通过 `submenu` 字段识别叶子节点,在多级下拉菜单中实现“有子菜单则仅展开、无子菜单则响应点击”的精准交互逻辑。

react 中实现多级递归菜单时,一个常见痛点是:如何让带子菜单的项仅响应悬停展开,而叶子节点(无子菜单)才真正响应点击跳转或触发操作? 你当前的 closeDropdown 逻辑失效,根本原因在于将交互控制权交给了组件层级状态(如 depthLevel 和 dropdown),而未回归数据本质——菜单项是否具备 submenu 属性,才是决定其可点击性的唯一可靠依据。

✅ 正确思路:以数据驱动交互,而非以递归深度驱动

React 组件的递归渲染本身不改变数据语义。每个 MenuItems 实例所接收的 items: MenuItemsI 对象已天然携带全部必要信息:

export interface MenuItemsI {   title: string;   submenu?: Array; // ← 关键!undefined 或空数组 = 叶子节点 }

因此,判断是否可点击只需一个纯函数:

const isLeaf = (item: MenuItemsI): boolean => !item.submenu || item.submenu.Length === 0;

⚠️ 注意:item.submenu?.length === 0 比单纯 !item.submenu 更健壮(兼容 submenu: [] 场景)。

重构 MenuItems:解耦点击与展开逻辑

将 onClick 行为从统一的 closeDropdown 中剥离,改为按数据分支处理:

function MenuItems({ items, depthLevel }: { items: MenuItemsI; depthLevel: number }) {   const [dropdown, setDropdown] = useState(false);   const ref = useRef(null);    // ✅ 悬停控制:仅对非顶层项启用(避免移动端误触)   const handleMouseEnter = () => {     if (depthLevel > 0 && items.submenu?.length) {       setDropdown(true);     }   };    const handleMouseLeave = () => {     if (depthLevel > 0) {       setDropdown(false);     }   };    // ✅ 点击控制:仅叶子节点执行业务逻辑(如跳转、触发事件)   const handleClick = (e: React.MouseEvent) => {     e.preventDefault(); // 阻止默认行为(尤其对 Link 内部点击)     if (isLeaf(items)) {       console.log("✅ 叶子节点被点击:", items.title);       // ? 此处替换为你的真实逻辑:history.push、openModal、dispatch 等       // alert(`跳转到 ${items.title}`);       return;     }     // 非叶子节点:点击仅切换自身展开状态(顶层仍可 toggle)     if (depthLevel === 0) {       setDropdown(prev => !prev);     }   };    return (     

  • {items.submenu?.length ? ( <>
    {items.title} {dropdown ? : }

  • text=ZqhQzanResources