React Router v4 introduced a new declarative, component based approach to routing. With that approach came some confusion around nested routes. In this post we’ll break down that confusion and you’ll learn how to nest routes with React Router.
React Router v4引入了一种新的基于组件的声明式路由方法。 通过这种方法,嵌套路线周围出现了一些混乱。 在这篇文章中,我们将消除这种混乱,您将学习如何使用React Router嵌套路由。
Also, I’ve created a video to go with this article if you’d prefer that.
另外,如果您愿意的话,我还创建了一个与本文一起播放的视频。
In order to understand recursion, you first need to be familiar with functions, return values, and the callstack. Similar, in order to understand nested routes, you first need to be comfortable with React Router’s most fundamental component, <Route>
.
为了理解递归,您首先需要熟悉函数,返回值和调用栈。 类似地,为了理解嵌套路由,您首先需要熟悉React Router的最基本组件<Route>
。
<Rou
te> takes in
a path and a com
ponent. When your app’s current location matches th
e path, the com
ponent will be rendered. When it doesn’t,
Route will render null.
<Rou
TE>取in
一个路径and a com
Ponent(波纳恩特)。 当您应用的当前位置s th
path匹配时, the com
组件将被呈现。 如果没有sn't,
Route将呈现null。
The way I like to think is whenever you use <Route path='/path' component={X}
/>, it’s always going to render something. If the app’s location matches th
e path, it’ll render the com
ponent. If it doesn’t, it will rende
r null.
我想想的方式是,每当您使用<Route path='/path' component={X}
/>时,它总是会渲染某些东西。 如果应用程序的位置s th
路径匹配,则会重新租借r the com
组件。 如果不是这样,它会[R ende
[R空。
I realize we’re starting off slow here but if you fully grasp that last paragraph, the rest of this tutorial will be ?.
我知道我们从这里开始很慢,但是如果您完全掌握了最后一段,那么本教程的其余部分将是?。
With that out of the way, let’s take a look at the example we’ll building. The idea is we have a list of topics, those topics have resources, and those resources have a url. Here’s the data structure we’re working with.
顺便说一句,让我们看一下我们将要构建的示例。 这个想法是我们有一个主题列表,那些主题有资源,而那些资源有url。 这是我们正在使用的数据结构。
const topics = [ { name: 'React Router', id: 'react-router', description: 'Declarative, component based routing for React', resources: [ { name: 'URL Parameters', id: 'url-parameters', description: "URL parameters are parameters whose values are set dynamically in a page's URL. This allows a route to render the same component while passing that component the dynamic portion of the URL so it can change based off of it.", url: 'https://tylermcginnis.com/react-router-url-parameters' }, { name: 'Programatically navigate', id: 'programmatically-navigate', description: "When building an app with React Router, eventually you'll run into the question of navigating programmatically. The goal of this post is to break down the correct approaches to programmatically navigating with React Router.", url: 'https://tylermcginnis.com/react-router-programmatically-navigate/' } ] }, { name: 'React.js', id: 'reactjs', description: 'A JavaScript library for building user interfaces', resources: [ { name: 'React Lifecycle Events', id: 'react-lifecycle', description: "React Lifecycle events allow you to tie into specific phases of a component's life cycle", url: 'https://tylermcginnis.com/an-introduction-to-life-cycle-events-in-react-js/' }, { name: 'React AHA Moments', id: 'react-aha', description: "A collection of 'Aha' moments while learning React.", url: 'https://tylermcginnis.com/react-aha-moments/' } ] }, { name: 'Functional Programming', id: 'functional-programming', description: 'In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.', resources: [ { name: 'Imperative vs Declarative programming', id: 'imperative-declarative', description: 'A guide to understanding the difference between Imperative and Declarative programming.', url: 'https://tylermcginnis.com/imperative-vs-declarative-programming/' }, { name: 'Building User Interfaces with Pure Functions and Function Composition', id: 'fn-composition', description: 'A guide to building UI with pure functions and function composition in React', url: 'https://tylermcginnis.com/building-user-interfaces-with-pure-functions-and-function-composition-in-react-js/' } ] }]
This schema will map nicely to nested routes and our app will eventually end up looking like this
此架构将很好地映射到嵌套路由,我们的应用最终将看起来像这样
I encourage you to play around with the full example HERE.
我鼓励您在这里尝试完整的示例。
Before we start worrying about nested routes, let’s first create the skeleton of our app including the navbar which will allow us to navigate between Home (/
) and Topics (/topics
).
在开始担心嵌套路由之前,让我们首先创建应用程序的框架,包括导航栏,该导航栏将使我们能够在Home( /
)和Topics( /topics
)之间导航。
import React, { Component } from 'react'import { BrowserRouter as Router, Link, Route // for later} from 'react-router-dom'
class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/topics'>Topics</Link></li> </ul> </div> </Router> ) }}
export default App
That’s a solid start. Now what we want to do is render a few <Rou
te>s so that the UI will change based on the current path we’re on. However, before we can do that, we need to go ahead and build out the components that we’ll pass to our &
lt;Route>s to render if there’s a match. - we’ll ca
ll them Hom
e and Topics. For now, while we get things set up, let’s just have both of them render a header which says
either HO
ME or TOPICS.
这是一个坚实的开始。 现在,我们要做的是渲染一些<Rou
te>,以便UI会根据我们当前所在的路径进行更改。 但是,在我们能够做到这一点之前,我们需要继续构建如果匹配的话,我们将传递给o our &
lt; Route>的组件以进行渲染。 - we'l l ca
LL日em Hom
e和主题。 现在,在设置好东西的同时,让它们都渲染一个标头,上面says
her HO
ME”或“ TOPICS”。
function Home () { return ( <h1> HOME </h1> )}
function Topics () { return ( <h1> TOPICS </h1> )}
Perfect. Now, we’ll use the <Rou
te> component to tell React Router that whenever someone i
s on /, it should render th
e Home component and whenever someone is on /
topics, it should render the
Topics component.
完善。 现在,我们将使用<Rou
德>组件告诉阵营路由器,每当有人i
的开/,应该仁德r th
e家成分,每当有人is on /
主题,它应该仁德r the
主题组件。
class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/topics'>Topics</Link></li> </ul>
<hr />
<Route exact path='/' component={Home} /> <Route path='/topics' component={Topics} /> </div> </Router> ) }}
At this point we have a very simple example of what we talked about earlier, ”<Rou
te> takes in
a path and a com
ponent. When your app’s current location matches th
e path, the com
ponent will be rendered. When it doesn’t,
Route will render null.”
在这一点上,我们有一个非常简单的示例,说明了我们之前所讨论的内容,“ <Rou
te>采用in
一条路径and a com
组件。 当您应用的当前位置s th
path匹配时, the com
组件将被呈现。 如果没有sn't,
Route将呈现null。”
If we’re at /
, we’ll see the navbar and the Home
component. If we’re at /topics
, we’ll see the navbar and the Topics
component.
如果位于/
,则将看到导航栏和Home
组件。 如果位于/topics
,将看到导航栏和Topics
组件。
Finally we have a good enough fountaion to start talking about how we go about dealing with nested routes with React Router. Thanks for your patience ?.
最后,我们有足够的基础开始讨论如何使用React Router处理嵌套路由。 谢谢你的耐心 ?。
If you look back to the final example, you’ll notice that when we go to /topics
, the UI we get is another navbar. Let’s modify our Topics
component to do just that. This time instead of hard coding our Link
s, we’ll need to use our topics
array to create a Link
for each high level topic.
如果回头看最后一个示例 ,您会注意到,当我们转到/topics
,得到的UI是另一个导航栏。 让我们修改Topics
组件来做到这一点。 这次而不是对我们的Link
进行硬编码,我们需要使用topics
数组为每个高级主题创建一个Link
。
function Topics () { return ( <div> <h1>Topics</h1> <ul> {topics.map(({ name, id }) => ( <li key={id}> <Link to={`/topics/${id}`}>{name}</Link> </li> ))} </ul> </div> )}
Now, when we go to /topics
and the Topics
component is rendered, we’ll get three Link
s each representing a high level topic. Notice where we’re linking to, /topics/${id}
. If we’re going to link someone to /topics/${id}
, that means we need to render a Route
which is going to match at that path. This is the first big concept of nested routes with React Router - it doesn’t matter if you render a Route
in your main component or in nested components, if the path
matches, the component
will be rendered. With that in mind, what if we render a Route
inside of our Topics
component and have it match for /topics/:topicId
?
现在,当我们转到/topics
并呈现Topics
组件时,我们将获得三个Link
每个Link
代表一个高级主题。 注意我们链接到的/topics/${id}
。 如果我们要将某人链接到/topics/${id}
,则意味着我们需要渲染一条要在该路径上匹配的Route
。 这是使用React Router嵌套路由的第一个重要概念-在主组件或嵌套组件中渲染Route
都没关系,如果path
匹配,则将渲染该component
。 考虑到这一点,如果我们在Topics
组件内部渲染Route
并使其与/topics/:topicId
匹配, /topics/:topicId
怎么/topics/:topicId
?
function Topic () { return ( <div> TOPIC </div> )}
function Topics () { return ( <div> <h1>Topics</h1> <ul> {topics.map(({ name, id }) => ( <li key={id}> <Link to={`/topics/${id}`}>{name}</Link> </li> ))} </ul>
<hr />
<Route path={`/topics/:topicId`} component={Topic}/> </div> )}
??????
??????
This is why understanding Route
was so important. Nothing has changed from earlier to right now, but for some reason, your brain gets all worked up since we’re rendering a Route
outside of our main App
component.
这就是为什么了解Route
如此重要的原因。 从早期到现在没有任何变化,但是由于某些原因,由于我们在主App
组件之外渲染Route
,因此您的大脑全部工作了。
What’s going on is when we go to /topics
, the Topic
component is rendered. Topics
then renders a navbar and a new Route
which will match for any of the Link
s in the navbar we just rendered (since the Link
s are linking to /topics/${id}
and the Route
is matching for /topics/:topicId
). This means that if we click on any of the Link
s in the Topics
component, the Topic
component is going to be rendered which right now just says that word Topic
.
这是当我们转到/topics
,将呈现Topic
组件。 然后Topics
会渲染一个导航栏和一个新的Route
,这将与我们刚渲染的navbar中的任何Link
匹配(因为Link
链接到/topics/${id}
,并且Route
匹配/topics/:topicId
)。 这意味着,如果我们单击Topics
组件中的任何Link
,则将呈现Topic
组件,该组件现在仅显示单词Topic
。
It’s important to note that just because we matched another
Route
component, that doesn’t mean the previousRoute
s that matched aren’t still rendered. This is what confuses a lot of people. Remember, think ofRoute
as rendering another component or null. The same way you think of nesting normal components in React can apply directly to nestingRoute
s.重要的是要注意,仅仅因为我们匹配了另一个
Route
组件,这并不意味着仍旧无法渲染与之匹配的先前Route
。 这就是使很多人困惑的地方。 请记住,将Route
视为呈现另一个组件或为null。 您在React中嵌套普通组件的想法可以直接应用于嵌套Route
。
At this point we’re progressing along nicely. What if, for some reason, another member of your team who wasn’t familiar with React Router decided to change /topics
to /concepts
? They’d probably head over to the main App
component and change the Route
在这一点上,我们进展顺利。 如果由于某种原因,您团队中不熟悉React Router的另一名成员决定将/topics
更改为/concepts
呢? 他们可能会前往主要的App
组件并更改Route
// <Route path='/topics' component={Topics} /><Route path='/concepts' component={Topics} />
NBD. The problem is, this totally breaks the app. Inside of the Topics
component we’re assuming that the path begins with /topics
but now it’s been changed to /concepts
. What we need is a way for the Topics
component to receive whatever the initial path as a prop. That way, regardless of if someone changes the parent Route
, it’ll always just work.
NBD。 问题是,这完全破坏了应用程序。 在Topics
组件内部,我们假设路径以/topics
开头,但现在已更改为/concepts
。 我们需要的是让Topics
组件接收任何作为道具的初始路径的方法。 这样,无论是否有人更改了父Route
,它都将始终有效。
Good news for us is React Router does exactly this. Each time a component is rendered with React Router, that component is passed three props - location
, match
, and history
. The one we care about is match
. match
is going to contain information about how the Route
was matches (exactly what we need). Specifically, it has two properties we need, path
and url
. These are very similar, this is how the docs describe them -
对我们来说,好消息是React Router确实做到了这一点。 每次使用React Router渲染一个组件时,该组件都会传递三个props: location
, match
和history
。 我们关心的是match
。 match
将包含有关Route
如何匹配的信息(正是我们需要的信息)。 具体来说,它具有我们需要的两个属性: path
和url
。 这些非常相似,这就是文档描述它们的方式-
path - The path pattern used to match. Useful for building nested <Rout
e>s
path - The path pattern used to match. Useful for building nested <Rout
path - The path pattern used to match. Useful for building nested <Rout
e>
url - The matched portion of the URL. Useful for building nested <Lin
k>s
url - The matched portion of the URL. Useful for building nested <Lin
url - The matched portion of the URL. Useful for building nested <Lin
k>
There’s one important insight in those definitions. Use match.path
for building nested Route
s and use match.url
for building nested Link
s.
这些定义有一个重要的见解。 使用match.path
构建嵌套Route
并使用match.url
构建嵌套Link
。
The best way to answer “why” is to look at an example.
回答“为什么”的最好方法是看一个例子。
If you’re not familiar with URL Parameters, head over to React Router v4: URL Parameters before continuing.
如果您不熟悉URL参数,请继续之前先转到React Router v4:URL参数 。
Assume we were using an app that had nested route’s and the current URL was /topics/react-router/url-parameters
.
假设我们使用的应用程序嵌套了路由,当前URL为/topics/react-router/url-parameters
。
If we were to log match.path
and match.url
in the most nested component, here’s what we would get.
如果我们将match.path
和match.url
在嵌套最多的组件中,这就是我们所得到的。
render() { const { match } = this.props // coming from React Router.
console.log(match.path) // /topics/:topicId/:subId
console.log(match.url) // /topics/react-router/url-parameters
return ...}
Notice that path
is including the URL parameters and url
is just the full URL. This is why one is used for Link
s and the other used for Route
s.
请注意, path
包括URL参数,而url
只是完整URL。 这就是为什么一个用于Link
,另一个用于Route
的原因。
When you’re creating a nested link, you don’t want to use URL paramters. You want the user to literally go to /topics/react-router/url-parameters
. That’s why match.url
is better for nested Link
s. However, when you’re matching certain patters with Route
, you want to include the URL parameters - that’s why match.path
is used for nested Route
s.
创建嵌套链接时,您不想使用URL参数。 您希望用户从字面上转到/topics/react-router/url-parameters
。 这就是为什么match.url
更适合嵌套Link
的原因。 但是,当您将某些模式与Route
匹配时,您想要包括URL参数-这就是为什么match.path
用于嵌套Route
的原因。
Let’s head back to our example, as of right now, we’re hard coding /topics
into our Route
and Link
s.
让我们回到我们的示例,到目前为止,我们正在将/topics
硬编码到Route
和Link
。
function Topics () { return ( <div> <h1>Topics</h1> <ul> {topics.map(({ name, id }) => ( <li key={id}> <Link to={`/topics/${id}`}>{name}</Link> </li> ))} </ul>
<hr />
<Route path={`/topics/:topicId`} component={Topic}/> </div> )}
But as we just talked about, because Topics
is rendered by React Router, it’s given a match
prop we can use. We’ll replace the /topics
portion of our Link
with match.url
and the /topics
portion of our Route
with match.path
.
但是正如我们刚才所说的那样,因为Topics
是由React Router渲染的,所以给了我们可以使用的match
道具。 我们将更换/topics
我们的部分Link
与match.url
和/topics
我们的部分Route
与match.path
。
function Topics ({ match }) { return ( <div> <h1>Topics</h1> <ul> {topics.map(({ name, id }) => ( <li key={id}> <Link to={`${match.url}/${id}`}>{name}</Link> </li> ))} </ul>
<hr />
<Route path={`${match.path}/:topicId`} component={Topic}/> </div> )}
?. Good work.
? 辛苦了
At this point our app is about half way done. We still need to add a few more layers of nesting. Here’s the good news — there’s nothing more you’re going to learn in this tutorial. Everything we need to do to finish out the remaining nested routes we’ve already talked about. We’ll continue to create new nested navbars, continue to render Route
s and we’ll continue to use match.url
and match.path
. If you’re comfortable at this point, the rest is gravy.
至此,我们的应用程序大约完成了一半。 我们仍然需要添加更多的嵌套层。 这是个好消息-在本教程中您将学到的更多东西。 我们需要做的所有事情来完成我们已经讨论过的其余嵌套路线。 我们将继续创建新的嵌套导航栏,继续渲染Route
并继续使用match.url
和match.path
。 如果您现在感到舒适,剩下的就是肉汁。
Now just as we initially did with the Topics
component, we want to make it so Topic
(no s) will also render a nested navbar and a Route
. The only difference is now we’re one level deeper so we’ll map over the topic’s resources
for our Link
s and we’ll our Route
will match /topics/:topicId/subId
.
现在,就像我们最初对Topics
组件所做的一样,我们希望做到这一点,因此Topic
(否)还将呈现嵌套的导航栏和Route
。 唯一的区别是,现在我们更深入了一层,因此我们将在Link
的主题resources
进行映射,并且Route
将与/topics/:topicId/subId
。
function Resource () { return <p>RESOURCE</p>}
function Topic ({ match }) { const topic = topics.find(({ id }) => id === match.params.topicId)
return ( <div> <h2>{topic.name}</h2> <p>{topic.description}</p>
<ul> {topic.resources.map((sub) => ( <li key={sub.id}> <Link to={`${match.url}/${sub.id}`}>{sub.name}</Link> </li> ))} </ul>
<hr />
<Route path={`${match.path}/:subId`} component={Resource} /> </div> )}
Finally the last thing we need to do it finish out our Resource
component. Because this is the last child component, we’ll no longer be rendering any more Link
s or Route
s. Instead, we’ll just give it a basic UI including the name of the resource, the description, and a (normal) link.
最后,我们要做的最后一件事是完成Resource
组件。 因为这是最后一个子组件,所以我们将不再渲染任何Link
或Route
。 取而代之的是,我们只给它一个基本的UI,包括资源的名称,描述和一个(常规)链接。
function Resource ({ match }) { const topic = topics.find(({ id }) => id === match.params.topicId) .resources.find(({ id }) => id === match.params.subId)
return ( <div> <h3>{topic.name}</h3> <p>{topic.description}</p> <a href={topic.url}>More info.</a> </div> )}
Here’s the full code.
这是完整的代码。
import React, { Component } from 'react'import { BrowserRouter as Router, Route, Link,} from 'react-router-dom'
const topics = [ { name: 'React Router', id: 'react-router', description: 'Declarative, component based routing for React', resources: [ { name: 'URL Parameters', id: 'url-parameters', description: "URL parameters are parameters whose values are set dynamically in a page's URL. This allows a route to render the same component while passing that component the dynamic portion of the URL so it can change based off of it.", url: 'https://tylermcginnis.com/react-router-url-parameters' }, { name: 'Programatically navigate', id: 'programmatically-navigate', description: "When building an app with React Router, eventually you'll run into the question of navigating programmatically. The goal of this post is to break down the correct approaches to programmatically navigating with React Router.", url: 'https://tylermcginnis.com/react-router-programmatically-navigate/' } ] }, { name: 'React.js', id: 'reactjs', description: 'A JavaScript library for building user interfaces', resources: [ { name: 'React Lifecycle Events', id: 'react-lifecycle', description: "React Lifecycle events allow you to tie into specific phases of a components lifecycle", url: 'https://tylermcginnis.com/an-introduction-to-life-cycle-events-in-react-js/' }, { name: 'React AHA Moments', id: 'react-aha', description: "A collection of 'Aha' moments while learning React.", url: 'https://tylermcginnis.com/react-aha-moments/' } ] }, { name: 'Functional Programming', id: 'functional-programming', description: 'In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.', resources: [ { name: 'Imperative vs Declarative programming', id: 'imperative-declarative', description: 'A guide to understanding the difference between Imperative and Declarative programming.', url: 'https://tylermcginnis.com/imperative-vs-declarative-programming/' }, { name: 'Building User Interfaces with Pure Functions and Function Composition', id: 'fn-composition', description: 'A guide to building UI with pure functions and function composition in React', url: 'https://tylermcginnis.com/building-user-interfaces-with-pure-functions-and-function-composition-in-react-js/' } ] }]
function Resource ({ match }) { const topic = topics.find(({ id }) => id === match.params.topicId) .resources.find(({ id }) => id === match.params.subId)
return ( <div> <h3>{topic.name}</h3> <p>{topic.description}</p> <a href={topic.url}>More info.</a> </div> )}
function Topic ({ match }) { const topic = topics.find(({ id }) => id === match.params.topicId)
return ( <div> <h2>{topic.name}</h2> <p>{topic.description}</p>
<ul> {topic.resources.map((sub) => ( <li key={sub.id}> <Link to={`${match.url}/${sub.id}`}>{sub.name}</Link> </li> ))} </ul>
<hr />
<Route path={`${match.path}/:subId`} component={Resource} /> </div> )}
function Topics ({ match }) { return ( <div> <h1>Topics</h1> <ul> {topics.map(({ name, id }) => ( <li key={id}> <Link to={`${match.url}/${id}`}>{name}</Link> </li> ))} </ul>
<hr />
<Route path={`${match.path}/:topicId`} component={Topic}/> </div> )}
function Home () { return ( <h1> Home. </h1> )}
class App extends Component { render() { return ( <Router> <div style={{width: 1000, margin: '0 auto'}}> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/topics'>Topics</Link></li> </ul>
<hr />
<Route exact path='/' component={Home} /> <Route path='/topics' component={Topics} /> </div> </Router> ) }}
export default App
Congrats! You now have the power to create nested routes with React Router. Tell your Mom, she’ll be proud.
恭喜! 现在,您可以使用React Router创建嵌套路由。 告诉你妈妈,她会感到骄傲。
Note that this article just one part of my comprehensive new React Router course.
请注意,本文只是我全面的新React Router课程的一部分 。
Follow me on Twitter — @tylermcginnis and read more of my writing on tylermcginnis.com.
在Twitter上关注我— @tylermcginnis,并在tylermcginnis.com上阅读我的更多文章 。
翻译自: https://www.freecodecamp.org/news/nested-routes-with-react-router-v4-f1c313c62b03/