简体   繁体   English

带有反应路由器 v4 / v5 的嵌套路由

[英]Nested routes with react router v4 / v5

I am currently struggling with nesting routes using react router v4.我目前正在努力使用反应路由器 v4 嵌套路由。

The closest example was the route config in the React-Router v4 Documentation .最接近的示例是React-Router v4 文档中的路由配置。

I want to split my app in 2 different parts.我想将我的应用分成 2 个不同的部分。

A frontend and an admin area.一个前端和一个管理区域。

I was thinking about something like this:我在想这样的事情:

<Match pattern="/" component={Frontpage}>
  <Match pattern="/home" component={HomePage} />
  <Match pattern="/about" component={AboutPage} />
</Match>
<Match pattern="/admin" component={Backend}>
  <Match pattern="/home" component={Dashboard} />
  <Match pattern="/users" component={UserPage} />
</Match>
<Miss component={NotFoundPage} />

The frontend has a different layout and style than the admin area.前端的布局和样式与管理区域不同。 So within the frontpage the route home, about and so one should be the child routes.所以在首页的路线首页,关于等应该是子路线。

/home should be rendered into the Frontpage component and /admin/home should be rendered within the Backend component. /home应该呈现在 Frontpage 组件中,而/admin/home应该呈现在 Backend 组件中。

I tried some other variations but I always ended in not hitting /home or /admin/home .我尝试了其他一些变化,但我总是以没有点击/home/admin/home结束。

Final solution:最终解决方案:

This is the final solution I am using right now.这是我现在使用的最终解决方案。 This example also has a global error component like a traditional 404 page.这个例子还有一个像传统 404 页面一样的全局错误组件。

import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';

const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>

const Frontend = props => {
  console.log('Frontend');
  return (
    <div>
      <h2>Frontend</h2>
      <p><Link to="/">Root</Link></p>
      <p><Link to="/user">User</Link></p>
      <p><Link to="/admin">Backend</Link></p>
      <p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/' component={Home}/>
        <Route path='/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

const Backend = props => {
  console.log('Backend');
  return (
    <div>
      <h2>Backend</h2>
      <p><Link to="/admin">Root</Link></p>
      <p><Link to="/admin/user">User</Link></p>
      <p><Link to="/">Frontend</Link></p>
      <p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/admin' component={Home}/>
        <Route path='/admin/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

class GlobalErrorSwitch extends Component {
  previousLocation = this.props.location

  componentWillUpdate(nextProps) {
    const { location } = this.props;

    if (nextProps.history.action !== 'POP'
      && (!location.state || !location.state.error)) {
        this.previousLocation = this.props.location
    };
  }

  render() {
    const { location } = this.props;
    const isError = !!(
      location.state &&
      location.state.error &&
      this.previousLocation !== location // not initial render
    )

    return (
      <div>
        {          
          isError
          ? <Route component={Error} />
          : <Switch location={isError ? this.previousLocation : location}>
              <Route path="/admin" component={Backend} />
              <Route path="/" component={Frontend} />
            </Switch>}
      </div>
    )
  }
}

class App extends Component {
  render() {
    return <Route component={GlobalErrorSwitch} />
  }
}

export default App;

In react-router-v4 you don't nest <Routes /> .在 react-router-v4 中,您不会嵌套<Routes /> Instead, you put them inside another <Component /> .相反,您将它们放在另一个<Component />中。


For instance例如

<Route path='/topics' component={Topics}>
  <Route path='/topics/:topicId' component={Topic} />
</Route>

should become应该成为

<Route path='/topics' component={Topics} />

with

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <Link to={`${match.url}/exampleTopicId`}>
      Example topic
    </Link>
    <Route path={`${match.path}/:topicId`} component={Topic}/>
  </div>
) 

Here is a basic example straight from the react-router documentation .这是直接来自 react-router 文档基本示例

react-router v6

Update for 2022 - v6 has nested Route components that Just Work™. 2022 年更新- v6 包含 Just Work™ 的嵌套Route组件。

This question is about v4/v5, but the best answer now is just use v6 if you can!这个问题是关于 v4/v5 的,但现在最好的答案是尽可能使用 v6!

See example code in this blog post .请参阅此博客文章中的示例代码。 If you can't upgrade just yet, however...但是,如果您还不能升级...

react-router v4 & v5

It's true that in order to nest Routes you need to place them in the child component of the Route.确实,为了嵌套 Route,您需要将它们放置在 Route 的子组件中。

However if you prefer a more inline syntax rather than breaking your Routes up across components, you can provide a functional component to the render prop of the Route you want to nest under.但是,如果您更喜欢内联语法而不是跨组件分解路由,则可以为要嵌套在其下的 Route 的render prop 提供一个功能组件。

<BrowserRouter>

  <Route path="/" component={Frontpage} exact />
  <Route path="/home" component={HomePage} />
  <Route path="/about" component={AboutPage} />

  <Route
    path="/admin"
    render={({ match: { url } }) => (
      <>
        <Route path={`${url}/`} component={Backend} exact />
        <Route path={`${url}/home`} component={Dashboard} />
        <Route path={`${url}/users`} component={UserPage} />
      </>
    )}
  />

</BrowserRouter>

If you're interested in why the render prop should be used, and not the component prop, it's because it stops the inline functional component from being remounted on every render.如果您对为什么应该使用render属性而不是component属性感兴趣,那是因为它阻止了内联功能组件在每次渲染时重新安装。 See the documentation for more detail.有关更多详细信息, 请参阅文档

Note the example wraps the nested Routes in a Fragment .请注意,该示例将嵌套的 Routes 包装在Fragment中。 Prior to React 16, you can use a container <div> instead.在 React 16 之前,您可以使用容器<div>代替。

Just wanted to mention react-router v4 changed radically since this question was posted/answed.只是想提一下,自从发布/回答了这个问题以来,react-router v4 发生了根本性的变化。

There is no <Match> component any more!不再有<Match>组件! <Switch> is to make sure only the first match is rendered. <Switch>是为了确保只渲染第一个匹配项。 <Redirect> well .. redirects to another route. <Redirect>好吧 .. 重定向到另一条路线。 Use or leave out exact to either in- or exclude a partial match.使用或省略exact匹配或排除部分匹配。

See the docs.请参阅文档。 They are great.他们都是伟大的。 https://reacttraining.com/react-router/ https://reacttraining.com/react-router/

Here's an example I hope is useable to answer your question.这是一个例子,我希望可以用来回答你的问题。

<Router>
  <div>
    <Redirect exact from='/' to='/front'/>
    <Route path="/" render={() => {
      return (
        <div>
          <h2>Home menu</h2>
          <Link to="/front">front</Link>
          <Link to="/back">back</Link>
        </div>
      );
    }} />          
    <Route path="/front" render={() => {
      return (
        <div>
        <h2>front menu</h2>
        <Link to="/front/help">help</Link>
        <Link to="/front/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/front/help" render={() => {
      return <h2>front help</h2>;
    }} />
    <Route exact path="/front/about" render={() => {
      return <h2>front about</h2>;
    }} />
    <Route path="/back" render={() => {
      return (
        <div>
        <h2>back menu</h2>
        <Link to="/back/help">help</Link>
        <Link to="/back/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/back/help" render={() => {
      return <h2>back help</h2>;
    }} />
    <Route exact path="/back/about" render={() => {
      return <h2>back about</h2>;
    }} />
  </div>
</Router>

Hope it helped, let me know.希望它有帮助,让我知道。 If this example is not answering your question well enough, tell me and I'll see if I can modify it.如果这个例子不能很好地回答你的问题,请告诉我,我会看看我是否可以修改它。

I succeeded in defining nested routes by wrapping with Switch and define nested route before than root route.我通过使用Switch包装成功地定义了嵌套路由,并在根路由之前定义了嵌套路由。

<BrowserRouter>
  <Switch>
    <Route path="/staffs/:id/edit" component={StaffEdit} />
    <Route path="/staffs/:id" component={StaffShow} />
    <Route path="/staffs" component={StaffIndex} />
  </Switch>
</BrowserRouter>

Reference: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md参考: https ://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md

Using Hooks使用钩子

The latest update with hooks is to use useRouteMatch .钩子的最新更新是使用useRouteMatch

Main routing component主路由组件


export default function NestingExample() {
  return (
    <Router>
      <Switch>
       <Route path="/topics">
         <Topics />
       </Route>
     </Switch>
    </Router>
  );
}

Child component子组件

function Topics() {
  // The `path` lets us build <Route> paths 
  // while the `url` lets us build relative links.

  let { path, url } = useRouteMatch();

  return (
    <div>
      <h2>Topics</h2>
      <h5>
        <Link to={`${url}/otherpath`}>/topics/otherpath/</Link>
      </h5>
      <ul>
        <li>
          <Link to={`${url}/topic1`}>/topics/topic1/</Link>
        </li>
        <li>
          <Link to={`${url}/topic2`}>/topics/topic2</Link>
        </li>
      </ul>

      // You can then use nested routing inside the child itself
      <Switch>
        <Route exact path={path}>
          <h3>Please select a topic.</h3>
        </Route>
        <Route path={`${path}/:topicId`}>
          <Topic />
        </Route>
        <Route path={`${path}/otherpath`>
          <OtherPath/>
        </Route>
      </Switch>
    </div>
  );
}

Some thing like this.像这样的东西。

 import React from 'react'; import { BrowserRouter as Router, Route, NavLink, Switch, Link } from 'react-router-dom'; import '../assets/styles/App.css'; const Home = () => <NormalNavLinks> <h1>HOME</h1> </NormalNavLinks>; const About = () => <NormalNavLinks> <h1>About</h1> </NormalNavLinks>; const Help = () => <NormalNavLinks> <h1>Help</h1> </NormalNavLinks>; const AdminHome = () => <AdminNavLinks> <h1>root</h1> </AdminNavLinks>; const AdminAbout = () => <AdminNavLinks> <h1>Admin about</h1> </AdminNavLinks>; const AdminHelp = () => <AdminNavLinks> <h1>Admin Help</h1> </AdminNavLinks>; const AdminNavLinks = (props) => ( <div> <h2>Admin Menu</h2> <NavLink exact to="/admin">Admin Home</NavLink> <NavLink to="/admin/help">Admin Help</NavLink> <NavLink to="/admin/about">Admin About</NavLink> <Link to="/">Home</Link> {props.children} </div> ); const NormalNavLinks = (props) => ( <div> <h2>Normal Menu</h2> <NavLink exact to="/">Home</NavLink> <NavLink to="/help">Help</NavLink> <NavLink to="/about">About</NavLink> <Link to="/admin">Admin</Link> {props.children} </div> ); const App = () => ( <Router> <div> <Switch> <Route exact path="/" component={Home}/> <Route path="/help" component={Help}/> <Route path="/about" component={About}/> <Route exact path="/admin" component={AdminHome}/> <Route path="/admin/help" component={AdminHelp}/> <Route path="/admin/about" component={AdminAbout}/> </Switch> </div> </Router> ); export default App;

A complete answer for React Router v6 or version 6 just in case needed. React Router v6 或版本 6 的完整答案,以备不时之需。

import Dashboard from "./dashboard/Dashboard";
import DashboardDefaultContent from "./dashboard/dashboard-default-content";
import { Route, Routes } from "react-router";
import { useRoutes } from "react-router-dom";

/*Routes is used to be Switch*/
const Router = () => {

  return (
    <Routes>
      <Route path="/" element={<LandingPage />} />
      <Route path="games" element={<Games />} />
      <Route path="game-details/:id" element={<GameDetails />} />
      <Route path="dashboard" element={<Dashboard />}>
        <Route path="/" element={<DashboardDefaultContent />} />
        <Route path="inbox" element={<Inbox />} />
        <Route path="settings-and-privacy" element={<SettingsAndPrivacy />} />
        <Route path="*" element={<NotFound />} />
      </Route>
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};
export default Router;
import DashboardSidebarNavigation from "./dashboard-sidebar-navigation";
import { Grid } from "@material-ui/core";
import { Outlet } from "react-router";

const Dashboard = () => {
  return (
    <Grid
      container
      direction="row"
      justify="flex-start"
      alignItems="flex-start"
    >
      <DashboardSidebarNavigation />
      <Outlet />
    </Grid>
  );
};

export default Dashboard;

Github repo is here. Github 仓库在这里。 https://github.com/webmasterdevlin/react-router-6-demo https://github.com/webmasterdevlin/react-router-6-demo

React Router v6反应路由器v6

allows to use both nested routes (like in v3) and separate, splitted routes (v4, v5).允许使用嵌套路由(如在 v3 中)和单独的拆分路由(v4、v5)。

Nested Routes嵌套路由

Keep all routes in one place for small/medium size apps:对于中小型应用程序,将所有路线放在一个地方

<Routes>
  <Route path="/" element={<Home />} >
    <Route path="user" element={<User />} /> 
    <Route path="dash" element={<Dashboard />} /> 
  </Route>
</Routes>

 const App = () => { return ( <BrowserRouter> <Routes> // /js is start path of stack snippet <Route path="/js" element={<Home />} > <Route path="user" element={<User />} /> <Route path="dash" element={<Dashboard />} /> </Route> </Routes> </BrowserRouter> ); } const Home = () => { const location = useLocation() return ( <div> <p>URL path: {location.pathname}</p> <Outlet /> <p> <Link to="user" style={{paddingRight: "10px"}}>user</Link> <Link to="dash">dashboard</Link> </p> </div> ) } const User = () => <div>User profile</div> const Dashboard = () => <div>Dashboard</div> ReactDOM.render(<App />, document.getElementById("root"));
 <div id="root"></div> <script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5.0.0/umd/history.production.min.js"></script> <script src="https://unpkg.com/react-router@6.0.0-alpha.5/umd/react-router.production.min.js"></script> <script src="https://unpkg.com/react-router-dom@6.0.0-alpha.5/umd/react-router-dom.production.min.js"></script> <script>var { BrowserRouter, Routes, Route, Link, Outlet, useNavigate, useLocation } = window.ReactRouterDOM;</script>

Alternative: Define your routes as plain JavaScript objects via useRoutes .替代方案:通过useRoutes将您的路由定义为纯 JavaScript 对象。

Separate Routes单独的路线

You can use separates routes to meet requirements of larger apps like code splitting:您可以使用分隔路由来满足大型应用程序的需求,例如代码拆分:

// inside App.jsx:
<Routes>
  <Route path="/*" element={<Home />} />
</Routes>

// inside Home.jsx:
<Routes>
  <Route path="user" element={<User />} />
  <Route path="dash" element={<Dashboard />} />
</Routes>

 const App = () => { return ( <BrowserRouter> <Routes> // /js is start path of stack snippet <Route path="/js/*" element={<Home />} /> </Routes> </BrowserRouter> ); } const Home = () => { const location = useLocation() return ( <div> <p>URL path: {location.pathname}</p> <Routes> <Route path="user" element={<User />} /> <Route path="dash" element={<Dashboard />} /> </Routes> <p> <Link to="user" style={{paddingRight: "5px"}}>user</Link> <Link to="dash">dashboard</Link> </p> </div> ) } const User = () => <div>User profile</div> const Dashboard = () => <div>Dashboard</div> ReactDOM.render(<App />, document.getElementById("root"));
 <div id="root"></div> <script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5.0.0/umd/history.production.min.js"></script> <script src="https://unpkg.com/react-router@6.0.0-alpha.5/umd/react-router.production.min.js"></script> <script src="https://unpkg.com/react-router-dom@6.0.0-alpha.5/umd/react-router-dom.production.min.js"></script> <script>var { BrowserRouter, Routes, Route, Link, Outlet, useNavigate, useLocation } = window.ReactRouterDOM;</script>

You can try something like Routes.js您可以尝试类似 Routes.js

import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom';
import FrontPage from './FrontPage';
import Dashboard from './Dashboard';
import AboutPage from './AboutPage';
import Backend from './Backend';
import Homepage from './Homepage';
import UserPage from './UserPage';
class Routes extends Component {
    render() {
        return (
            <div>
                <Route exact path="/" component={FrontPage} />
                <Route exact path="/home" component={Homepage} />
                <Route exact path="/about" component={AboutPage} />
                <Route exact path="/admin" component={Backend} />
                <Route exact path="/admin/home" component={Dashboard} />
                <Route exact path="/users" component={UserPage} />    
            </div>
        )
    }
}

export default Routes

App.js应用程序.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Routes from './Routes';

class App extends Component {
  render() {
    return (
      <div className="App">
      <Router>
        <Routes/>
      </Router>
      </div>
    );
  }
}

export default App;

I think you can achieve the same from here also.我认为你也可以从这里实现同样的目标。

A complete answer for React Router v5. React Router v5 的完整答案。


const Router = () => {
  return (
    <Switch>
      <Route path={"/"} component={LandingPage} exact />
      <Route path={"/games"} component={Games} />
      <Route path={"/game-details/:id"} component={GameDetails} />
      <Route
        path={"/dashboard"}
        render={({ match: { path } }) => (
          <Dashboard>
            <Switch>
              <Route
                exact
                path={path + "/"}
                component={DashboardDefaultContent}
              />
              <Route path={`${path}/inbox`} component={Inbox} />
              <Route
                path={`${path}/settings-and-privacy`}
                component={SettingsAndPrivacy}
              />
              <Redirect exact from={path + "/*"} to={path} />
            </Switch>
          </Dashboard>
        )}
      />
      <Route path="/not-found" component={NotFound} />
      <Redirect exact from={"*"} to={"/not-found"} />
    </Switch>
  );
};

export default Router;
const Dashboard = ({ children }) => {
  return (
    <Grid
      container
      direction="row"
      justify="flex-start"
      alignItems="flex-start"
    >
      <DashboardSidebarNavigation />
      {children}
    </Grid>
  );
};

export default Dashboard;

Github repo is here. Github 仓库在这里。 https://github.com/webmasterdevlin/react-router-5-demo https://github.com/webmasterdevlin/react-router-5-demo

I prefer to use react function.我更喜欢使用反应功能。 This solution is short and more readable该解决方案简短且更具可读性

const MainAppRoutes = () => (
    <Switch>
        <Route exact path='/' component={HomePage} />
        {AdminRoute()}                  
        {SampleRoute("/sample_admin")}  
    </Switch>
);

/*first implementation: without params*/
const AdminRoute = () => ([
    <Route path='/admin/home' component={AdminHome} />,
    <Route path='/admin/about' component={AdminAbout} />
]);

/*second implementation: with params*/
const SampleRoute = (main) => ([
    <Route path={`${main}`} component={MainPage} />,
    <Route path={`${main}/:id`} component={MainPage} />
]); 

**This code worked for me with v6**

index.js index.js

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />}>
          <Route path="login" element={<Login />} />
          <Route path="home" element={<Home />} />
        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

App.js:应用程序.js:

function App(props) {
  useEffect(() => {
    console.log('reloaded');
// Checking, if Parent component re-rendering or not *it should not be, in the sense of performance*, this code doesn't re-render parent component while loading children
  });
  return (
    <div className="App">
      <Link to="login">Login</Link>
      <Link to="home">Home</Link>
      <Outlet /> // This line is important, otherwise we will be shown with empty component
    </div>
  );
}

login.js:登录.js:

const Login = () => {
    return (
        <div>
            Login Component
        </div>
    )
};

home.js:主页.js:

const Home= () => {
    return (
        <div>
            Home Component
        </div>
    )
};

在这里我已经在TSX中创建了一个示例,如果有人想要摆脱子路径路径中包装路由的前缀: https//stackoverflow.com/a/47891060/5517306

interface IDefaultLayoutProps {
    children: React.ReactNode
}

const DefaultLayout: React.SFC<IDefaultLayoutProps> = ({children}) => {
    return (
        <div className="DefaultLayout">
            {children}
        </div>
    );
}


const LayoutRoute: React.SFC<IDefaultLayoutRouteProps & RouteProps> = ({component: Component, layout: Layout, ...rest}) => {
const handleRender = (matchProps: RouteComponentProps<{}, StaticContext>) => (
        <Layout>
            <Component {...matchProps} />
        </Layout>
    );

    return (
        <Route {...rest} render={handleRender}/>
    );
}

const ScreenRouter = () => (
    <BrowserRouter>
        <div>
            <Link to="/">Home</Link>
            <Link to="/counter">Counter</Link>
            <Switch>
                <LayoutRoute path="/" exact={true} layout={DefaultLayout} component={HomeScreen} />
                <LayoutRoute path="/counter" layout={DashboardLayout} component={CounterScreen} />
            </Switch>
        </div>
    </BrowserRouter>
);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM