React-router、antd實現同步瀏覽器地址高亮對應選單

風霜秋月發表於2019-03-04

關於 React 和 antd 元件庫

React 是目前主流的前端開發框架,目前前端流行的框架是 Angular,Vue,React,具體選型看專案需求而定。

antd 是基於 React 開發的元件庫,有螞蟻金服團隊退出,目前使用人數較多,元件也比較多,文件也很友好。

本次我做的就是使用 antd 的 Menu 元件搭配 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;
複製程式碼

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. 元件編寫

    1. 實現思路:

    (1).為了實現不限級別的路由渲染及高亮選單,我這裡使用的是遞迴實現。

    (2).當瀏覽器地址改變後要高亮對應選單項,這個可能是一級選單,也可能是子級選單,所以還需要展開相應的子選單

    (3).監聽瀏覽器地址變化,使用 react-router 渲染的元件在 props 中會接收 history 的物件,這個物件有一個 listen 方法,可以新增自定義監聽事件,預設接收引數為一個物件:{
    hash: “”
    pathname: “”
    search: “”
    state: undefined
    }

    1. 開始實現
    // 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>
        );
      }
    }
    複製程式碼
  2. 效果圖

React-router、antd實現同步瀏覽器地址高亮對應選單

相關文章