React Router v4 / v5中的嵌套路由

369

我目前在使用React Router v4时遇到了嵌套路由的问题。

最接近的例子是在React-Router v4文档中的路由配置。

我想将我的应用程序分成两个不同的部分:前端和管理区域。

我考虑的大致方案如下:

<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} />
前端页面的布局和样式与管理区域不同。因此,在前端页面中,主页、关于等路由应该是子路由。 应该将/home呈现为Frontpage组件,并在Backend组件中呈现/admin/home。 我尝试了一些其他的变化,但我总是无法访问/home/admin/home

2
感谢您更新问题并提供最终答案!只是一个建议:也许您可以只保留第四个列表和第一个列表,因为其他列表使用的是过时的API版本,会分散注意力。 - Giuliano Vilela
2
哈哈,我不知道这个日期是什么格式:08.05.2017。如果你不想让人们感到困惑,我建议你使用通用的ISO8601日期格式。08是月份还是日期?ISO8601 = 年.月.日 时.分.秒(逐渐更精细) - wesm
1
不错的最终更新解决方案,但我认为您不需要“previousLocation”逻辑。 - tudorpavel
1
完全重写React Router 的动机是什么?最好有一个充分的理由。 - Oliver Watkins
1
这是声明式的方法。因此,您可以像使用React组件一样设置路由。 - datoml
1
请勿在问题中写下答案,而是创建一个答案。不必标记“更新”,因为历史修订已记录。 - Kalle Richter
13个回答

401
在react-router-v4中,你不需要嵌套<Routes />。相反,你需要把它们放在另一个<Component />里面。
例如
<Route path='/topics' component={Topics}>
  <Route path='/topics/:topicId' component={Topic} />
</Route>

应该变成

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

使用

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

这里有一个直接从react-router的文档中取得的基础示例


1
我能够从你的链接中实现基本示例,但是当我手动输入URL时,在我的本地服务器上它无法工作。但是在你的示例中可以。另一方面,当我手动输入带有#的URL时,HashRouter可以正常工作。你有任何想法为什么在我的本地服务器上手动输入URL时BrowserRouter不能工作吗? - himawan_r
10
你能将Topics组件转化为一个类吗?'match'参数是从哪里传进来的?是在render()函数里吗? - user1076813
31
似乎很荒谬,你不能只是使用 ${match.url} 隐式地将 to="exampleTopicId" 设置为目标。 - greenimpala
9
根据文档 https://reacttraining.com/react-router/web/example/route-config,您可以设置嵌套路由。这将允许按照文档中所述的主题来集中配置路由。如果没有此功能,在较大的项目中管理这些路由将会非常困难。 - JustDave
6
这些不是嵌套路由,而是一级路由,仍然使用一个Route的render属性,该属性以函数组件作为输入。请仔细观察,从React Router < 4的角度来看,这里没有嵌套。RouteWithSubRoutes是一个一级路由列表,它使用模式匹配。 - Lyubomir
显示剩余4条评论

210

React-Router v6

2022年更新 - v6有嵌套的Route组件,可直接使用。

本问题涉及v4/v5版本,但现在最好的答案是:如果可以,请使用v6!

查看此博客文章中的示例代码。但如果暂时无法升级...

React-Router v4 & v5

确实,为了嵌套路由,您需要将它们放置在Route的子组件中。

但是如果您更喜欢更内联的语法而不是在组件之间分割您的Routes,您可以向要嵌套在其下的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>

如果你想知道为什么应该使用 render 属性而不是 component 属性,那是因为它可以防止内联函数组件在每次渲染时重新挂载。详情请参考文档

请注意,示例将嵌套的路由包装在一个Fragment中。在 React 16 之前,您可以使用一个容器 <div> 来代替。


30
感谢上帝,唯一的解决方案相对清晰、易于维护,并且按预期运行。我希望React Router 3嵌套路由能够回来。 - Merunas Grincalaitis
这个看起来完美,像是Angular的路由出口。 - Partha Ranjan Nayak
7
你应该使用 match.path 而不是 match.url。前者通常用于 Route 的 path 属性中,而后者用于推送新路由时(例如 Link 的 to 属性)。 - nbkhope
最后我找到了我要找的东西。非常感谢。 - gogagubi
我缺少了 exact - Zeeshan Ahmad Khalil
显示剩余3条评论

54

想提一下自从这个问题被发布/回答以来,react-router v4发生了巨大的变化

现在已经没有<Match>组件了!<Switch>用于确保只呈现第一个匹配项。<Redirect>可以重定向到另一个路由。使用或不使用exact来包含或排除部分匹配。

请查看文档。它们很棒。https://reacttraining.com/react-router/

以下是我希望能够回答您的问题的可用示例。

<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>
希望这有所帮助,请告诉我。如果这个例子没有充分回答你的问题,请告诉我,我会看看能否进行修改。

在 https://reacttraining.com/react-router/web/api/Redirect 上,Redirect 没有 exact 属性。如果有的话,代码会更简洁,就像 <Route exact path="/" render={() => <Redirect to="/path" />} /> 这样。但至少 TypeScript 不会让我这么做。 - Filuren
2
我理解得对吗,现在没有嵌套/子路由这种东西了吗?我需要在所有路由中复制基础路由吗?React-Router 4没有提供任何有助于以可维护的方式构建路由的帮助吗? - Ville
5
@Ville,我很惊讶,你找到更好的解决方案了吗?我不想路线到处都是,天啊。 - punkbit
1
这个会起作用,但是请确保在webpack配置文件中将public path设置为“/”以便于bundle.js,否则嵌套路由在页面刷新时将无法正常工作。 - vikrant

13

重新排列顺序解决了我的问题,虽然我不知道这是否会产生任何副作用。但目前可以工作..谢谢 :) - Anbu369
3
请注意,尽管此解决方案适用于某些情况,但对于使用嵌套路由呈现分层布局组件的情况,该解决方案不起作用。在v3中,使用嵌套路由可以做一些很好的事情。 - Clinton Chau

11

使用Hooks

使用Hooks的最新更新是使用useRouteMatch

主路由组件


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

子组件

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>
  );
}


9

类似这样的东西。

(这段文字是示例,仅供参考)

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;


7
在必要时,为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代码库在这里。 https://github.com/webmasterdevlin/react-router-6-demo


1
请您也在这里加入DashboardSidebarNavigation,可以吗? - cfranklin

5

React路由v6

允许使用嵌套路由(类似于v3)和独立分割路由(v4、v5)。

嵌套路由

对于小型/中等大小的应用程序,将所有路由放在一个位置

<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>

替代方案:通过useRoutes将路由定义为普通的JavaScript对象。

分离路由

您可以使用分离路由来满足大型应用程序的要求,例如代码拆分:

// 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>


我认为使用 RoutesRoute 的方法已经过时了,我喜欢这种新的做法,我在 AngularJS 中也是用同样的方式,谢谢。 - Ashish Kamble

3
您可以尝试使用类似于 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

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;

我认为你也可以从这里实现相同的目标。

好观点!在使用Java Spring boot应用程序开发后,我开始学习React时也是这样想的。唯一需要改变的是Routes.js中的'div'改成'Switch'。说实话,你可以在App.js中定义所有路由,但是可以将其包装在外面,例如在index.js文件中(create-react-app)。 - Malakai
是的,你说得对!我已经实现了这种方式,所以我才提到这个方法。 - Aniruddh Agarwal

2
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 代码仓库链接。 https://github.com/webmasterdevlin/react-router-5-demo

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