# 实战篇 05:菜单匹配逻辑
本节参考代码: react-sider

在大部分企业管理系统中,页面的基础布局所采取的一般都是侧边栏菜单加页面内容这样的组织形式。在成熟的组件库支持下,UI 层面想要做出一个漂亮的侧边栏菜单并不困难,但因为在企业管理系统中菜单还承担着页面导航的功能,于是就导致了两大难题,一是多级菜单如何处理,二是菜单项的子页面(如点击门店管理中的某一个门店进入的门店详情页在菜单中并没有对应的菜单项)如何高亮其隶属于的父级菜单。
# 多级菜单
为了增强系统的可扩展性,企业管理系统中的菜单一般都需要提供多级支持,对应的数据结构就是在每一个菜单项中都要有 children 属性来配置下一级菜单项。
const menuData = [{
name: '仪表盘',
icon: 'dashboard',
path: 'dashboard',
children: [{
name: '分析页',
path: 'analysis',
children: [{
name: '实时数据',
path: 'realtime',
}, {
name: '离线数据',
path: 'offline',
}],
}],
}];
# 递归渲染父菜单及子菜单
想要支持多级菜单,首先要解决的问题就是如何统一不同级别菜单项的交互。
在大多数的情况下,每一个菜单项都代表着一个不同的页面路径,点击后会触发 url 的变化并跳转至相应页面,也就是上面配置中的 path 字段。

但对于一个父菜单来说,点击还意味着打开或关闭相应的子菜单,这就与点击跳转页面发生了冲突。为了简化这个问题,我们先统一菜单的交互为点击父菜单(包含 children 属性的菜单项)为打开或关闭子菜单,点击子菜单(不包含 children 属性的菜单项)为跳转至相应页面。
首先,为了成功地渲染多级菜单,菜单的渲染函数是需要支持递归的,即如果当前菜单项含有 children 属性就将其渲染为父菜单并优先渲染其 children 字段下的子菜单,这在算法上被叫做深度优先遍历。
renderMenu = data => (
map(data, (item) => {
if (item.children) {
return (
<SubMenu
key={item.path}
title={
<span>
<Icon type={item.icon} />
<span>{item.name}</span>
</span>
}
>
{this.renderMenu(item.children)}
</SubMenu>
);
}
return (
<Menu.Item key={item.path}>
<Link to={item.path} href={item.path}>
<Icon type={item.icon} />
<span>{item.name}</span>
</Link>
</Menu.Item>
);
})
)
这样我们就拥有了一个支持多级展开、子菜单分别对应页面路由的侧边栏菜单。细心的朋友可能还发现了,虽然父菜单并不对应一个具体的路由但在配置项中依然还有 path 这个属性,这是为什么呢?
# 处理菜单高亮
在传统的企业管理系统中,为不同的页面配置页面路径是一件非常痛苦的事情,对于页面路径,许多开发者唯一的要求就是不重复即可,如上面的例子中,我们把菜单数据配置成这样也是可以的。
const menuData = [{
name: '仪表盘',
icon: 'dashboard',
children: [{
name: '分析页',
children: [{
name: '实时数据',
path: '/realtime',
}, {
n