React 是目前主流的前端开发框架,目前前端流行的框架是 Angular,Vue,React,具体选型看项目需求而定。javascript
antd 是基于 React 开发的组件库,有蚂蚁金服团队退出,目前使用人数较多,组件也比较多,文档也很友好。css
本次我作的就是使用 antd 的 Menu 组件搭配 React,实现浏览器地址改变,高亮对应导航菜单的需求。前端
1.本次使用React-router-dom@4.3.1做为前端路由,为了方便,直接使用 HashRouter:java
// 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;
复制代码
2.路由配置node
//前端须要维护一份路由表,不管是静态配置亦或是由后端获取, 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'
}
]
}
]
}
]
}
];
复制代码
组件编写react
(1).为了实现不限级别的路由渲染及高亮菜单,我这里使用的是递归实现。后端
(2).当浏览器地址改变后要高亮对应菜单项,这个多是一级菜单,也多是子级菜单,因此还须要展开相应的子菜单浏览器
(3).监听浏览器地址变化,使用 react-router 渲染的组件在 props 中会接收 history 的对象,这个对象有一个 listen 方法,能够添加自定义监听事件,默认接收参数为一个对象:{ hash: "" pathname: "" search: "" state: undefined }antd
// 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>
);
}
}
复制代码
效果图react-router