React-Router + AntD/怎样在按下“后退/前进”按钮时高亮显示菜单项?

28

我创建了一个菜单,并想要突出显示我选择的项目,我成功地做到了。但当我按下“后退/前进”按钮时,菜单项不会突出显示。我该怎么办?

我尝试使用addEventListener,但失败了。

有人能给些建议吗?

class Sidebar extends React.Component {
    constructor(props) {
        super(props);
        this.state={
            test: "home"
        }
        this.menuClickHandle = this.menuClickHandle.bind(this);
    }

    componentWillMount(){
        hashHistory.listen((event)=>{
            test1 = event.pathname.split("/");
        });
        this.setState({
            test:test1[1]
        });
    }

    menuClickHandle(item) {
        this.props.clickItem(item.key);
    }

    onCollapseChange() {
        this.props.toggle();
    }

    render() {
        var {collapse} = this.props;
        return (
            <aside className="ant-layout-sider">
                <Menu mode="inline" theme="dark" defaultSelectedKeys={[this.state.test || "home"]} onClick={this.menuClickHandle.bind(this)}>
                    <Menu.Item key="home">
                        <Link to="/home">
                            <Icon type="user"/><span className="nav-text">用户管理</span>
                        </Link>
                    </Menu.Item>
                    <Menu.Item key="banner">
                        <Link to="/banner">
                            <Icon type="setting"/><span className="nav-text">Banner管理</span>
                        </Link>
                    </Menu.Item>
                </Menu>
                <div className="ant-aside-action" onClick={this.onCollapseChange.bind(this)}>
                    {collapse ? <Icon type="right"/> : <Icon type="left"/>}
                </div>
            </aside>
        )
    }
}
8个回答

52

我可以使用WithRouter提出一种解决方案。

import React,{ Component } from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { Layout, Menu, Icon } from 'antd';
import PropTypes from 'prop-types';

const { Sider } = Layout;

class SideMenu extends Component{

  static propTypes = {
    location: PropTypes.object.isRequired
  }

  render() {
    const { location } = this.props;
    return (
        <Sider
          trigger={null}
          collapsible
          collapsed={this.props.collapsed}>

          <div className="logo" />
          <Menu
            theme="dark"
            mode="inline"
            defaultSelectedKeys={['/']}
            selectedKeys={[location.pathname]}>
            <Menu.Item key="/">
              <NavLink to="/">
                <Icon type="home" />
                <span>Home</span>
              </NavLink>
            </Menu.Item>
            <Menu.Item key="/other">
              <NavLink to="/other">
                <Icon type="mobile"/>
                <span>Applications</span>
              </NavLink>
            </Menu.Item>
            <Menu.Item key="/notifications">
              <NavLink to="/notifications">
                <Icon type="notification" />
                <span>Notifications</span>
              </NavLink>
            </Menu.Item>
          </Menu>
        </Sider>
    )
  }
}

export default withRouter(SideMenu);

8
拦截当前URL,然后设置selectedKeys(注意不是defaultSelectedKeys)。
componentWillMount(){
        hashHistory.listen((event)=>{
            pathname = event.pathname.split("/");
            if(pathname != null){
                this.setState({
                    test:pathname[1]
                });
            }
        });
    }

4
非常感谢,我浪费了三个小时以为问题与React的生命周期有关。实际上应该使用 selectedKeys 而不是 defaultSelectedKeys。确实值得点赞。 - theprogrammer
你有没有想过如何解决 Angular 中的问题? - taha manar

8

你可以将链接路径作为每个菜单项的键。然后使用selectedKeys={this.props.location.pathname}来设置选中状态。

<Menu
  theme="light"
  mode='inline'
  selectedKeys={[this.props.location.pathname,]}
>
  <Menu.Item key={item.path} style={{float:'right'}}>
    <Link to={item.path}>{item.name}</Link>
  </Menu.Item>
  {menulist}
</Menu>

根据当前路径,项目将被设置为活动状态。 我添加了[]和尾随逗号,因为selectedKeys接受数组而this.props.location.pathname是一个字符串。我只是业余编码,不知道这是否可接受。


如果使用React Router4钩子,也可以这样工作:let location = useLocation(); - matinfo

6
以下答案假定您正在使用Hooks。我知道您在问题中没有提到,但这对其他人可能有用。此外,如果您有嵌套路径,例如/banner/this/is/nested,则此解决方案将起作用,并且不仅在按下后退和前进按钮时有效,而且在刷新当前页面时也有效:
import React, { useState, useEffect } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { Layout, Menu } from 'antd'

const { Sider } = Layout

const items = [
  { key: '1', label: 'Invoices', path: '/admin/invoices' },
  { key: '2', label: 'Service Details', path: '/admin/service-details' },
  { key: '3', label: 'Service Contract Details', path: '/admin/service-contract-details' },
  { key: '4', label: 'Cost Centers', path: '/admin/cost-centers' },
  { key: '5', label: 'Clients', path: '/admin/clients' },
  { key: '6', label: 'Vendors', path: '/admin/vendors' }
]

const Sidebar = () => {
  const location = useLocation()
  const history = useHistory()
  const [selectedKey, setSelectedKey] = useState(items.find(_item => location.pathname.startsWith(_item.path)).key)

  const onClickMenu = (item) => {
    const clicked = items.find(_item => _item.key === item.key)
    history.push(clicked.path)
  }

  useEffect(() => {
    setSelectedKey(items.find(_item => location.pathname.startsWith(_item.path)).key)
  }, [location])

  return (
    <Sider style={{ backgroundColor: 'white' }}>
      <h3 style={{ paddingLeft: '1rem', paddingTop: '1rem', fontSize: '1.25rem', fontWeight: 'bold', minHeight: 64, margin: 0 }}>
        Costek
      </h3>
      <Menu selectedKeys={[selectedKey]} mode='inline' onClick={onClickMenu}>
        {items.map((item) => (
          <Menu.Item key={item.key}>{item.label}</Menu.Item>
        ))}
      </Menu>
    </Sider>
  )
}

export default Sidebar


这是侧边栏的样子: 进入图像描述

如果你正在使用这个解决方案并得到类似于“useLocation()只能在<Router>组件的上下文中使用”的错误,请确保父组件被包装在一个<Router></Router>标签中。在我的情况下,我在<App></App>级别尝试了useLocation(),但它直到我将导航栏作为自己的功能组件并在那里定义逻辑后才起作用。 - Alex

2

@Nadun的解决方案适用于不包含参数的路径。如果您在路由中使用参数,比如我一样,这里有一个解决方案,可以适用于任何路由路径,包括/users/:id或者像/users/:id/whatever/:otherId这样的疯狂路径。它使用了react-router的matchPath API,该API使用与Router组件完全相同的逻辑。

// file with routes
export const ROUTE_KEYS = {
    ROOT: "/",
    USER_DETAIL: "/users/:id",
};

export const ROUTES = {
    ROOT: {
        component: Home,
        exact: true,
        key: ROUTE_KEYS.ROOT,
        path: ROUTE_KEYS.ROOT,
    },
    USER_DETAIL: {
        component: Users,
        key: ROUTE_KEYS.USER_DETAIL,
        path: ROUTE_KEYS.USER_DETAIL,
    },
};

.

// place within the App component
<Router>
    <Layout>
        <MyMenu />
        <Layout>
            <Layout.Content>
                {Object.values(ROUTES).map((route) => (
                    <Route {...route} />
                ))}
            </Layout.Content>
        </Layout>
    </Layout>
</Router>

.

// MyMenu component
const getMatchedKey = (location) =>
    (
        Object.values(ROUTES).find((route) =>
            matchPath(location.pathname, route)
        ) || {}
    ).path;

const MyMenu = ({ location }) => {
    return (
        <Layout.Sider>
            <AntMenu mode="inline" selectedKeys={[getMatchedKey(location)]}>
                <AntMenu.SubMenu
                    title={
                        <React.Fragment>
                            <Icon type="appstore" />
                            Home
                        </React.Fragment>
                    }
                >
                    <AntMenu.Item key={ROUTE_KEYS.ROOT}>
                        <Icon type="appstore" />
                        <span className="nav-text">
                            Some subitem
                        </span>
                    </AntMenu.Item>
                </AntMenu.SubMenu>
                <AntMenu.SubMenu
                    title={
                        <React.Fragment>
                            <Icon type="user" />
                            Users
                        </React.Fragment>
                    }
                >
                    <AntMenu.Item key={ROUTE_KEYS.USER_DETAIL}>
                        <Icon type="user" />
                        <span className="nav-text">
                            User detail
                        </span>
                    </AntMenu.Item>
                </AntMenu.SubMenu>
            </AntMenu>
        </Layout.Sider>
    );
};

export default withRouter(MyMenu);

0

环境:React Router V5Ant Design V4.17.0
我通过覆盖antdMenu.ItemonClick属性来解决这个问题。

            <Menu theme="light" mode="inline">
                {menuItems.map((item) => {
                    return (
                        <NavLink
                            to={item.navigation}
                            component={({ navigate, ...rest }) => <Menu.Item {...rest} onClick={navigate} />}
                            key={item.key}
                            activeClassName="ant-menu-item-selected"
                        >
                            {item.icons}
                            <span>{item.name}</span>
                        </NavLink>
                    )
                }
                )}
            </Menu>

NavLink 组件将会传递 navigate 属性到 Menu.Item,我们需要将其映射到 onClick 属性上,这样点击行为才能正常工作。


0
如果你正在使用一个数组并对其进行映射(就像我这种情况),以设置菜单项,它们必须与它们在侧边菜单中出现的顺序相同,否则将不会显示活动条或背景。

0
我做了类似的事情,但它似乎不是响应式的。比如,如果我通过按钮导航到一个新页面(而不是从菜单项),它将不会更新活动链接,直到页面刷新。
import React from 'react';
import { StyleSheet, css } from 'aphrodite'
import { browserHistory, Link } from 'react-router';
import 'antd/lib/menu/style/css';
import 'antd/lib/icon/style/css';
import 'antd/lib/row/style/css';
import 'antd/lib/col/style/css';
import 'antd/lib/message/style/css';
import { appConfig } from '../../modules/config';
import { Menu, Icon, Row, Col, message } from 'antd';

const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;


const { appName } = appConfig;




const AppNavigation = React.createClass({
  getInitialState() {
        return {
          current: this.props.pathname
        };

  },
  handleClick(e) {
    browserHistory.push(e.key);
    this.setState({ current: e.key });
    return;  
  },
  render() {
    return (
    <Row className='landing-menu' type="flex" justify="space-around" align="middle"  style={{height: 55, zIndex: 1000, paddingLeft: 95, color: '#fff', backgroundColor: '#da5347', borderBottom: '1px solid #e9e9e9'}}>
        <Col span='19'>
            <Link to='/'>
          <h2 style={{fontSize: 21, color: '#fff'}}>
            {appName}
            <Icon type="rocket" color="#fff" style={{fontWeight: 200, fontSize: 26, marginLeft: 5 }}/>
          </h2>
        </Link>
        </Col>
        <Col span='5'>
            <Menu onClick={this.handleClick} selectedKeys={[this.state.current]} mode="horizontal" style={{height: 54, backgroundColor: '#da5347', borderBottom: '0px solid transparent'}}>
            <Menu.Item style={{height: 54, }} key="/">Home</Menu.Item>
            <Menu.Item style={{height: 54, }} key="/signup">Signup</Menu.Item>
            <Menu.Item style={{height: 54, }} key="/login">Login</Menu.Item>
          </Menu>
        </Col>

      </Row>
    );
  },
});


export const App = React.createClass({

  propTypes: {
    children: React.PropTypes.element.isRequired,
  },
  componentWillMount(){
    if (Meteor.userId()) {
      browserHistory.push('/student/home')
    }
  },
  render() {

    return (
        <div style={{position: 'relative'}}>
          <AppNavigation pathname={this.props.location.pathname}  />
            <div style={{minHeight: '100vh'}}>
             { this.props.children }
            </div>
        </div>
    );
  }





});

编辑:

以下方法非常有效。从React-Router传递路径名,并将其作为属性传递到selectedKeys中。

import React from 'react';
import { StyleSheet, css } from 'aphrodite'
import { browserHistory, Link } from 'react-router';
import 'antd/lib/menu/style/css';
import 'antd/lib/icon/style/css';
import 'antd/lib/row/style/css';
import 'antd/lib/col/style/css';
import 'antd/lib/message/style/css';
import { appConfig } from '../../modules/config';
import { Menu, Icon, Row, Col, message } from 'antd';

const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;


const { appName } = appConfig;




const AppNavigation = React.createClass({
  getInitialState() {
        return {
          current: this.props.pathname
        };

  },
  handleClick(e) {
    browserHistory.push(e.key);
    this.setState({ current: e.key });
    return;  
  },
  render() {
    return (
    <Row className='landing-menu' type="flex" justify="space-around" align="middle"  style={{height: 55, zIndex: 1000, paddingLeft: 95, color: '#fff', backgroundColor: '#da5347', borderBottom: '1px solid #e9e9e9'}}>
        <Col span='19'>
            <Link to='/'>
          <h2 style={{fontSize: 21, color: '#fff'}}>
            {appName}
            <Icon type="rocket" color="#fff" style={{fontWeight: 200, fontSize: 26, marginLeft: 5 }}/>
          </h2>
        </Link>
        </Col>
        <Col span='5'>
            <Menu onClick={this.handleClick} selectedKeys={[this.props.pathname]} mode="horizontal" style={{height: 54, backgroundColor: '#da5347', borderBottom: '0px solid transparent'}}>
            <Menu.Item style={{height: 54, }} key="/">Home</Menu.Item>
            <Menu.Item style={{height: 54, }} key="/signup">Signup</Menu.Item>
            <Menu.Item style={{height: 54, }} key="/login">Login</Menu.Item>
          </Menu>
        </Col>

      </Row>
    );
  },
});


export const App = React.createClass({

  propTypes: {
    children: React.PropTypes.element.isRequired,
  },
  componentWillMount(){
    if (Meteor.userId()) {
      browserHistory.push('/student/home')
    }
  },
  render() {

    return (
        <div style={{position: 'relative'}}>
          <AppNavigation pathname={this.props.location.pathname}  />
            <div style={{minHeight: '100vh'}}>
             { this.props.children }
            </div>
        </div>
    );
  }


});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接