React Router with - Ant Design Sider:如何使用相关菜单项的组件填充内容部分

Mel*_*Mel 11 javascript reactjs antd react-router-dom

我正在尝试像标签面板一样使用 AntD 菜单侧边。

我想将组件放在内容中,以便在单击菜单项时内容面板呈现相关组件。

如何让这种结构将组件作为每个菜单项的内容?

import React from 'react';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { PasswordForgetForm } from '../Auth/Password/Forgot';


const { Title } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;


class Dashboard extends React.Component {
  state = {
    collapsed: false,
  };

  onCollapse = collapsed => {
    console.log(collapsed);
    this.setState({ collapsed });
  };

  render() {
    return (
      <Layout style={{ minHeight: '100vh' }}>
        <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
          <div  />
          <Menu theme="light" defaultSelectedKeys={['1']} mode="inline" >

            <Menu.Item key="1">
              <Icon type="fire" />
              <span>Next item</span>
              <PasswordForgetForm />
            </Menu.Item>  
            <Menu.Item key="2">
              <Icon type="fire" />
              <span>Next item</span>
              <Another component to render in the content div />
            </Menu.Item>

          </Menu>
        </Sider>
        <Layout>
          <Header style={{ background: '#fff', padding: 0 }} />
          <Content style={{ margin: '0 16px', background: '#fff' }}>

            <div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
            RENDER RELEVANT COMPONENT HERE BASED ON MENU ITEM SELECTED
            </div>
          </Content>
          </Layout>
      </Layout>
    );
  }
}

export default Dashboard;
Run Code Online (Sandbox Code Playgroud)

当我尝试呈现它时,不会抛出任何错误,但内容 div 不会使用我指定为菜单项内容的 PasswordForgetForm 进行更新。

我在下面尝试了 Chris 的建议 - 它可以很好地为布局段中的每个不同菜单项内容 div 呈现组件,但是 - 这种方法的缺点是每次页面刷新时,路径都会转到内容组件那个特定的菜单项,而不是上面有菜单的测试页面——以及具有相关内容的内容组件。如果这种方法被认可为明智的,有没有办法在原始页面上保持页面刷新,而不是尝试转到子菜单项 url?

更新

我找到了这个文献。我想这可能是我需要知道的,但是语言太技术性了,我无法理解。任何人都可以帮助解决这个主题的简单英文版本吗?

我还发现了这个使用姿势来帮助渲染的教程。虽然这种结构是我想要实现的,但看起来 Pose 已被弃用。有谁知道是否有必要超越 react-router 来获得这个结果,或者 react 中是否有我可以寻求实施的解决方案?

这个例子与我想要的相似,但我找不到任何定义侧边栏的东西(这似乎是使这项工作所必需的参数)。

我也看过这个帖子。虽然有些答案是文档的复制和粘贴,但我想知道 Adam Gering 的答案是否更接近我的需要。我试图始终将 Sider 菜单保留在 /dash 路线上。该 url 不应更改 - 无论用户单击侧边中的哪个菜单项,都应更新 /dash 组件中的内容 div 以在我指定的路由路径上呈现组件。

应用克里斯的建议

克里斯在下面友好地提出了一个建议。我试过了,但它没有按预期执行的情况是单击菜单项时(并且相关组件在内容 div 中正确加载,但是如果我刷新页面,刷新尝试加载一个页面用于应该位于仪表板页面上的内容 div 内的组件的 url。

import React from 'react';
import {
    BrowserRouter as Router,
    Route,
    Link,
    Switch,

  } from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';

// import UserName from '../Users/UserName';
import Account from '../Account/Index';
import Test from '../Test/Index';



const { Title } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;


class Dashboard extends React.Component {
  state = {
    collapsed: false,
  };

  onCollapse = collapsed => {
    console.log(collapsed);
    this.setState({ collapsed });
  };

  render() {
    const {  loading } = this.state;
    // const dbUser = this.props.firebase.app.snapshot.data();
    // const user = Firebase.auth().currentUser;
    return (
    <AuthUserContext.Consumer>
      {authUser => (  

        <div>    
         {authUser.email}
          <Router>  
            <Layout style={{ minHeight: '100vh' }}>
              <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
                <div  />
                <Menu theme="light" defaultSelectedKeys={['1']} mode="inline" >
                  <SubMenu
                    key="sub1"
                    title={
                      <span>
                      <Icon type="user" />
                      <span>Profile</span>
                      </span>
                    }
                  >

                      <Menu.Item key="2"><Link to={ROUTES.ACCOUNT}>Account Settings</Link></Menu.Item>


                      <Menu.Item key="3"><Link to={ROUTES.TEST}>2nd content component</Link></Menu.Item>
</SubMenu>

                </Menu>
              </Sider>
              <Layout>

                <Header>                </Header>      
                <Content style={{ margin: '0 16px', background: '#fff' }}>

                  <div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
                      <Switch>
                          <Route path={ROUTES.ACCOUNT}>
                          <Account />
                          </Route>
                          <Route path={ROUTES.TEST}>
                          < Test />
                          </Route>

                      </Switch>    
                  </div>
                </Content>
                <Footer style={{ textAlign: 'center' }}>


                 test footer
                </Footer>
              </Layout>
            </Layout>
          </Router>  
        </div>
      )}
    </AuthUserContext.Consumer>  
    );
  }
}

export default Dashboard;
Run Code Online (Sandbox Code Playgroud)

在我的路线文件中,我有:

export const ACCOUNT = '/account';
Run Code Online (Sandbox Code Playgroud)

我还发现了这个教程——它使用了上面概述的相同方法——并且不会像我的代码在页面刷新时那样被重定向。本教程使用 Route 而不是 BrowserRouter - 但除此之外我看不出任何区别。

下一次尝试

我看到了这篇文章(感谢马特)。我试图遵循该帖子中的建议。

我从仪表板页面中删除了外部路由器包装器(App.js 周围有一个路由器,这是设置仪表板路由的地方)。

然后,在我的仪表板中,我将内容 div 更改为:

我补充说:

let match = useRouteMatch();
Run Code Online (Sandbox Code Playgroud)

到渲染方法我的控制面板组件内(如图所示在这里)。

我在 react-router-dom 的 import 语句中添加了 useRouteMatch。

这会产生一个错误,指出:

错误:无效的挂钩调用。钩子只能在函数组件的主体内部调用。这可能是由于以下原因之一而发生的: 1. 你的 React 和渲染器的版本可能不匹配(例如 React DOM) 2. 你可能违反了 Hooks 规则

上面的错误中有更多消息,但堆栈溢出不允许我发布它,因为它有一个短链接

我不知道我在这些步骤中做错了什么,但是如果我注释掉 let 语句,页面就会加载。

当我转到/仪表板并单击“帐户”时,我希望帐户组件在仪表板页面布局中的内容 div 内呈现。相反,它直接重定向到位于 localhost3000/account 的页面(没有页面引用——它只是一个在仪表板页面内呈现的组件)。

所以这比我开始的问题更糟糕 - 因为至少在开始时,重定向只发生在页面刷新时。现在它立即发生。

我发现这个 repo有每种路线的例子。我看不出它在做什么而我不是。

这篇文章似乎和我有同样的问题,并使用与我尝试过的方法相同的方法解决了它。这是 2018 年的帖子,所以时间的流逝可能会使该方法过时 - 但我看不出该帖子解决方案实现的内容与我最初的尝试之间有任何区别。

下一次尝试

我原以为我找到了一种适用于 Lyubomir 对这篇文章的回答的方法。

我有:

<Menu.Item key="2">
                      <Link to=
Run Code Online (Sandbox Code Playgroud)

{ ${this.props.match.url}/account}> 账户设置

然后在我想显示这个组件的破折号组件的 div 中,我有:

<div>
                      <Switch>
                          <Route exact path={`${this.props.match.url}/:account`} component={Account} />
Run Code Online (Sandbox Code Playgroud)

这有效。在页面刷新时,我可以保持正常工作。

但是,当我添加第二个菜单项时:

<Menu.Item key="16">
                    <Link to={`${this.props.match.url}/learn`}>
                      <Icon type="solution" />
                      <span>Lern</span>
                    </Link>  
                  </Menu.Item>
Run Code Online (Sandbox Code Playgroud)

当单击 /learn 的菜单项(并且 url 栏更改为学习)时,不是呈现 Learning 组件,而是呈现 Account 组件。如果我从 switch 语句中删除帐户显示,那么我可以显示正确的学习组件。

有了这次尝试,我有菜单项工作,以配合仪表盘URL扩展(即本地主机/仪表/帐户或本地主机/仪表/学习)用于ONE仅菜单项。如果我添加第二个菜单项,我可以正确呈现第二个组件的唯一方法是删除第一个菜单项。我正在使用具有精确路径的开关。

我希望此解决方案适用于多个菜单项。

我已经尝试过 url 的交替路径(例如:

<Link to={`${this.props.match.url}/learn`}>
Run Code Online (Sandbox Code Playgroud)

是相同的:

<Link to={`${this.props.match.path}/learn`}>
Run Code Online (Sandbox Code Playgroud)

我已经阅读了这个博客中的解释,虽然我并不完全理解这些选项。我读过这个。它表明 match 语句现在是渲染组件的传统方法(现在钩子可用)。我找不到任何说明如何使用钩子来实现预期结果的培训材料。

Chr*_* Su 9

更好的解决方案是使用 React Router<Link>将每个菜单项链接到特定路径,然后在内容中,使用<Switch>来渲染相应的组件。这是文档:React 路由器

使用 React Router 渲染

<Router>
  <Layout style={{ minHeight: "100vh" }}>
    <Sider
      collapsible
      collapsed={this.state.collapsed}
      onCollapse={this.onCollapse}
    >
      <div />
      <Menu theme="light" defaultSelectedKeys={["1"]} mode="inline">
        <Menu.Item key="1">
          // Add a react router link here
          <Link to="/password-forget-form">
            <Icon type="fire" />
            <span>Next item</span>
          </Link>
        </Menu.Item>
        <Menu.Item key="2">
          // Add another react router link
          <Link to="/next-item">
            <Icon type="fire" />
            <span>Next item</span>
          </Link>
        </Menu.Item>
      </Menu>
    </Sider>
    <Layout>
      <Header style={{ background: "#fff", padding: 0 }} />
      <Content style={{ margin: "0 16px", background: "#fff" }}>
        <div style={{ padding: 24, background: "#fff", minHeight: 360 }}>
          // Render different components based on the path
          <Switch>
            <Route path="/password-forget-form">
              <PasswordForgetForm />
            </Route>
            <Route path="/next-item">
              <Another component to render in the content div />
            </Route>
          </Switch>
        </div>
      </Content>
    </Layout>
  </Layout>
</Router>;
Run Code Online (Sandbox Code Playgroud)

使用菜单键渲染

应用程序.js

import React, { useState } from "react";
import { Layout } from "antd";
import Sider from "./Sider";
import "./styles.css";

const { Content } = Layout;
export default function App() {
  const style = {
    fontSize: "30px",
    height: "100%",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  };

  const components = {
    1: <div style={style}>Option 1</div>,
    2: <div style={style}>Option 2</div>,
    3: <div style={style}>Option 3</div>,
    4: <div style={style}>Option 4</div>
  };

  const [render, updateRender] = useState(1);

  const handleMenuClick = menu => {
    updateRender(menu.key);
  };

  return (
    <div className="App">
      <Layout style={{ minHeight: "100vh" }}>
        <Sider handleClick={handleMenuClick} />
        <Layout>
          <Content>{components[render]}</Content>
        </Layout>
      </Layout>
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

Sider.js

import React from "react";
import { Menu, Layout, Icon } from "antd";

const { SubMenu } = Menu;

export default function Sider(props) {
  const { handleClick } = props;
  return (
    <Layout.Sider>
      <Menu theme="dark" mode="inline" openKeys={"sub1"}>
        <SubMenu
          key="sub1"
          title={
            <span>
              <Icon type="mail" />
              <span>Navigation One</span>
            </span>
          }
        >
          <Menu.Item key="1" onClick={handleClick}>
            Option 1
          </Menu.Item>
          <Menu.Item key="2" onClick={handleClick}>
            Option 2
          </Menu.Item>
          <Menu.Item key="3" onClick={handleClick}>
            Option 3
          </Menu.Item>
          <Menu.Item key="4" onClick={handleClick}>
            Option 4
          </Menu.Item>
        </SubMenu>
      </Menu>
    </Layout.Sider>
  );
}
Run Code Online (Sandbox Code Playgroud)

编辑 antd-menu-click-rendering


Ani*_*l_M 6

我正在使用antd >=4.20.0, 因为副作用Menu.Item已被弃用。虽然上述解决方案有效,但它确实警告有关停止支持。所以这不是一个长期的解决方案。

请在此处查看有关 4.20.0 更改的详细信息。

长期解决方案是Menu.Item我们需要使用items={[]}. 但这不起作用,<Link>因为 API 不支持,我们无法将其作为子组件传递。

我环顾四周,经过大量搜索并结合各种来源的信息,有效的是,

  1. 用于useNavigate()使用 中的 onClick 来更改页面Menu items[]
  2. 用于useLocation().pathname获取路径并将其传递为selectedKey
  3. 在 中定义路线<Content>

这是工作示例[剥离到骨架代码以说明概念]

#index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';

<React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
  </React.StrictMode> 
Run Code Online (Sandbox Code Playgroud)

#App.js

import React, {useState} from 'react';
import { Layout, Menu, Tooltip } from 'antd'
import {
  MenuUnfoldOutlined,
  MenuFoldOutlined,
  TeamOutlined,
  UserOutlined,
} from '@ant-design/icons'
import { useLocation, useNavigate, Route, Routes } from 'react-router-dom'

import './App.less';
import 'antd/dist/antd.min.css';

const { Content, Sider } = Layout;

const Page1 = () => {
 return <h4> Page 1</h4>
}

const Page2 = () => {
  return <h4> Page 2</h4>
}


const App = () => {
  const [collapsed, setCollapsed] = useState(true);

  const toggleCollapsed = () => {
    setCollapsed(!collapsed);
  };

  let navigate = useNavigate();
  const selectedKey = useLocation().pathname

  const highlight = () => {
    if (selectedKey === '/'){
      return ['1']
    } else if (selectedKey === '/page2'){
      return ['2']
    }  
  }

 

  return (
    <Layout className="site-layout">
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className="logo"> 
            <Tooltip placement="right" arrowPointAtCenter  title="Expand / Shrink Menu" >
                {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
                    className: 'trigger',
                    onClick: toggleCollapsed,
                })}
            </Tooltip>
          </div>
          <Menu
            mode="inline"
            theme="light"
            defaultSelectedKeys={['1']}
            selectedKeys={highlight()}
            style={{ height: '100%', borderRight:0 }}
            items={[
              {
                key: '1',
                icon: <UserOutlined />,
                label: "Page 1",
                onClick: () => { navigate('/')}
              },
              {
                key: '2',
                icon: <TeamOutlined />,
                label: "Page 2",
                onClick: () => { navigate('/page2')}
              }

            ]}
            />
        </Sider>
        <Content>
          <Routes>
            <Route exact path="/" element={<Page1 />} />
            <Route path="/page2" element={<Page2 />} />
          </Routes>
        </Content>
    </Layout>
  )
}

export default App;
Run Code Online (Sandbox Code Playgroud)

#结果页面 在此输入图像描述


Mel*_*Mel 3

我在这篇文章中找到了柳博米尔的答案。有用。使用 React Router v4 / v5 的嵌套路由

菜单项链接为:

<Menu.Item key="2">
                      <Link to=
                       {`${this.props.match.url}/account`}>
                        Account
                      </Link>
                    </Menu.Item>
Run Code Online (Sandbox Code Playgroud)

显示路径为:

<Route exact path={`${this.props.match.path}/:account`} component={Account} />
Run Code Online (Sandbox Code Playgroud)

组件名称之前有一个冒号。不知道为什么。如果有人知道这种方法叫什么 - 我将不胜感激,以便我可以尝试理解它。