React Hooks实现点击其他菜单时关闭已展开的带子菜单的菜单
问题描述
我有一个包含菜单的侧边栏,部分菜单带有子菜单,希望实现点击不同菜单时,关闭所有已展开的带子菜单的菜单。
技术栈
React + React Hooks
当前代码
侧边栏组件
const [open, setOpen] = useState(true); return ( <div> <ul> {SidebarData.map((item, index) => { return ( <SubMenu item={item} key={index} titleOpen={!open} dropOpen={open} subMenuOpen={open} /> ); })} </ul> </div> );
子菜单组件
function SubMenu({ item, titleOpen, dropOpen, subMenuOpen }) { const [subnav, setSubnav] = useState(false); const showSubnav = () => setSubnav(!subnav); return ( <div> <ul> <Link to={item.path} onClick={item.subNav && showSubnav}> <li> <span>{item.icon}</span> <span> {item.title} </span> {item.subNav && dropOpen && ( <KeyboardArrowDownIcon/> )} </li> </Link> {subnav && subMenuOpen && item.subNav.map((item, index) => { return ( <ul> <Link to={item.path} key={index}> <li> <div>{item.title}</div> </li> </Link> </ul> ); })} </ul> </div> ); }
注:每个菜单拥有唯一ID
解决方案
核心思路是把子菜单的展开状态从子组件提升到父组件统一管理,实现点击其他菜单时自动关闭已展开项。
1. 修改侧边栏组件状态
替换原有open状态,改为记录当前展开菜单的唯一ID:
const [expandedMenuId, setExpandedMenuId] = useState(null); // 菜单点击处理逻辑 const handleMenuClick = (menuId) => { // 点击当前展开的菜单则关闭,否则展开当前菜单并关闭其他 setExpandedMenuId(prevId => prevId === menuId ? null : menuId); };
2. 传递状态与事件给子组件
渲染SubMenu时,传入当前展开状态和点击处理函数,同时用菜单唯一ID作为组件key:
{SidebarData.map((item) => { return ( <SubMenu item={item} key={item.id} isExpanded={expandedMenuId === item.id} onMenuClick={() => handleMenuClick(item.id)} titleOpen={!open} dropOpen={open} subMenuOpen={open} /> ); })}
3. 改造子菜单组件
移除子组件内部的subnav状态,改用父组件传递的isExpanded控制子菜单显示,点击事件委托给父组件处理:
function SubMenu({ item, titleOpen, dropOpen, subMenuOpen, isExpanded, onMenuClick }) { return ( <div> <ul> <Link to={item.path} onClick={() => { if (item.subNav) { onMenuClick(); } }} > <li> <span>{item.icon}</span> <span>{item.title}</span> {item.subNav && dropOpen && ( <KeyboardArrowDownIcon/> )} </li> </Link> {/* 用父组件传递的isExpanded替代原subnav状态 */} {isExpanded && subMenuOpen && item.subNav.map((subItem, index) => { return ( <ul key={index}> <Link to={subItem.path}> <li> <div>{subItem.title}</div> </li> </Link> </ul> ); })} </ul> </div> ); }
关键说明
- 状态提升:将子菜单展开状态统一由父组件管理,确保所有子菜单的展开状态同步,实现点击其他菜单时自动关闭已展开项。
- 唯一ID复用:用菜单唯一ID作为组件key,避免index导致的组件更新异常,同时方便匹配当前展开的菜单。
- 点击逻辑解耦:子菜单仅负责触发点击事件,状态更新逻辑交给父组件,降低组件耦合度。
内容的提问来源于stack exchange,提问作者King
相关产品推荐
相关产品推荐

