React-router、antd實現同步瀏覽器地址高亮對應選單
阿新 • • 發佈:2018-12-08
關於 React 和 antd 元件庫
React 是目前主流的前端開發框架,目前前端流行的框架是 Angular,Vue,React,具體選型看專案需求而定。
antd 是基於 React 開發的元件庫,有螞蟻金服團隊退出,目前使用人數較多,元件也比較多,文件也很友好。
本次我做的就是使用 antd 的 Menu 元件搭配 React,實現瀏覽器地址改變,高亮對應導航選單的需求。
具體實現
1.本次使用[email protected]作為前端路由,為了方便,直接使用 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;
複製程式碼
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).監聽瀏覽器地址變化,使用 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> ); } } 複製程式碼
-
效果圖