react-router与antd的Menu组件实现菜单高亮
tanhui 10/10/2018
js
react
关于需求
一般做后台管理系统,左边就是整个系统的入口,即菜单,通过点击菜单来到达具体的页面。所以导航菜单中需要高亮当前菜单,来告知用户当前所处的位置。但是如果用户直接从浏览器地址打开某个页面,这个时候菜单并没有高亮对应项,所以需要实现这个功能。
具体实现
1.本次使用React-router-dom@4.3.1作为前端路由,为了方便,直接使用 HashRouter:
// Layout/index.js
//引入必要的组件
import React, { PureComponent } from "react";
import { NavLink, Route, Switch, Redirect, Link } from "react-router-dom";
import { Menu, Icon } from "antd";
import "assets/layout/index.scss";
const MenuItem = Menu.Item;
const SubMenu = Menu.SubMenu;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
2.路由配置
//前端需要维护一份路由表,无论是静态配置亦或是由后端获取,
//antd的Menu组件使用key作为菜单项的唯一标识,这里我们直接使用path作为key(如果是子菜单则使用title作为key)
//当浏览器hash改变后可以更方便的获取到菜单项(注意,key一定不要重复,否则达不到效果)
//这里的菜单配置里子菜单可以任意级
const menuConfig = [
{
title: "首页",
icon: "pie-chart",
path: "/home"
},
{
title: "购买",
icon: null,
children: [
{
title: "详情",
icon: null,
path: "/buy/detail"
}
]
},
{
title: "管理",
icon: null,
children: [
{
title: "业绩",
icon: null,
children: [
{
title: "价格",
icon: null,
path: "/management/detail/price",
children: [
{
title: "股价",
icon: null,
path: "/management/ddd"
}
]
}
]
}
]
}
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
组件编写
- 实现思路: (1).为了实现不限级别的路由渲染及高亮菜单,我这里使用的是递归实现。
(2).当浏览器地址改变后要高亮对应菜单项,这个可能是一级菜单,也可能是子级菜单,所以还需要展开相应的子菜单
(3).监听浏览器地址变化,使用 react-router 渲染的组件在 props 中会接收 history 的对象,这个对象有一个 listen 方法,可以添加自定义监听事件,默认接收参数为一个对象:{hash: "",pathname: "",search: "",state: undefined}
- 开始实现
// 1.先定义一个菜单节点类,在下面初始化路由表数据的时候会用到: class MenuNode { constructor(menuItem, parent = null) { this.key = menuItem.path || menuItem.title; this.parent = parent; } } // 2. react组件 // defaultOpenKeys和defaultSelectedKeys是传递给Menu组件,用于指定当前打开的菜单项 export default class Index extends PureComponent { constructor(props) { super(props); this.state = { defaultOpenKeys: [], defaultSelectedKeys: [] }; this.menuTree = []; } componentDidMount = () => { const history = this.props.history; //初始化路由表: this.initMenu(menuConfig); //在渲染完成后需要手动执行一次此方法设置当前菜单,因为此时不会触发history的listen函数 this.setActiveMenu(history.location); this.unListen = history.listen(this.setActiveMenu); }; componentWillUnmount = () => { //移除监听 this.unListen(); }; //序列化路由表 initMenu = (config, parent = null) => { for (let menuItem of config) { if (menuItem.children) { //如果menuItem有children则对其children递归执行此方法,并且将当前menuItem作为父级 this.initMenu(menuItem.children, new MenuNode(menuItem, parent)); } else { //如果这个路由不是没有children,则是一级路由,则直接放入menuTree中 this.menuTree.push(new MenuNode(menuItem, parent)); } } /*menuTree中最终存储的是单个menuNode对象, 通过判断menuNode是否有效的parent即可判断是一级路由还是子菜单下的路由 */ }; //这个方法是实现菜单高亮的核心方法 setActiveMenu = location => { //拿到当前浏览器的hash路径 const pathname = location.pathname; // for (let node of this.menuTree) { /*使用正则判断当前浏览器path是否与菜单项中的key相匹配, 此正则可以匹配动态路径(类似于/product/:id这种传参的路由),所以即便是动态路由也能高亮对应菜单 */ const isActivePath = new RegExp(`^${node.key}`).test(pathname); if (isActivePath) { const openKeys = []; const selectedKeys = [node.key]; //判断当前菜单是否有父级菜单,如果有父级菜单需要将其展开 while (node.parent) { openKeys.push(node.parent.key); node = node.parent; } this.setState({ defaultOpenKeys: openKeys, defaultSelectedKeys: selectedKeys }); return; } } //如果一个路由都没有匹配上则关闭菜单 this.setState({ defaultSelectedKeys: [], defaultOpenKeys: [] }); }; //用于渲染路由,通过递归实现任意层级渲染 renderMenuItem = menuArr => { const ret = menuArr.map(item => { if (item.children) { return ( <SubMenu title={item.title} key={item.path || item.title}> {this.renderMenuItem(item.children)} </SubMenu> ); } else { return ( <MenuItem title={item.title} key={item.path}> <Link to="item.path">{item.title}</Link> </MenuItem> ); } }); return ret; }; render() { return ( <div> <div> <div style={{ width: 150 }}> <div>当前菜单:{this.state.defaultSelectedKeys[0]}</div> <Menu mode="inline" theme="dark" selectedKeys={this.state.defaultSelectedKeys} openKeys={this.state.defaultOpenKeys} onSelect={this.selectMenuItem} onOpenChange={this.openMenu} > {this.renderMenuItem(menuConfig)} </Menu> </div> </div> <div id="main">heelo,react</div> </div> ); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124效果图