fi3ework's Studio.

简明React Router v4教程

2017/10/28 Share

翻译自A Simple React Router v4 Tutorial

简明React Router v4教程

React Router v4是一个完全使用React重写的流行的React包,之前版本的React Router配置是使用伪组件也很晦涩难懂。现在使用v4版本的React Router,所有的东西都“仅仅是组件”。

在这个教程中,我们将建立一个本地的运动队,我们将完成所有的基本需求来建立我们的网站和路由,这包括:

  1. 选择router
  2. 创建routes
  3. 在路由之间通过链接进行导航

代码

Edit A Simple React Router v4 Tutorial

安装

React Router现在已经被划分成了三个包:react-routerreact-router-domreact-router-native

你不应该直接安装react-router,这个包为React Router应用提供了核心的路由组件和函数,另外两个包提供了特定环境的组件(浏览器和react-native对应的平台),不过他们也将react-router导出的功能再次导出。

你应该选择这两个中适应你开发环境的包,我们需要构建一个网站(在浏览器中运行),所以我们要安装react-router-dom

1
npm install --save react-router-dom

Router

当开始一个新项目时,你应该决定要使用哪种router。对于在浏览器中运行的项目,我们可以选择<BrowserRouter<HashRouter>组件,<BrowserRouter>应该用在服务器处理动态请求的项目中(知道如何处理任意的URI),<HashRouter>用来处理静态页面(只能响应请求已知文件的请求)。

通常来说更推荐使用<BrowserRouter>,可是如果服务器只处理静态页面的请求,那么使用<HashRouter>也是一个足够的解决方案。

对于我们的项目,我们假设所有的页面都是由服务器动态生成的,所以我们的router组件选择<BrowserRouter>

History

每个router都会创建一个history对象,用来保持对当前位置的追踪还有在页面发生变化的时候重新渲染页面。React Router提供的其他组件依赖在context上储存的history对象,所以他们必须在router对象的内部渲染。一个没有router祖先元素的React Router对象将无法正常工作,如果你想学习更多的关于history对象的知识,可以参照这篇文章

渲染一个<Router>

Router的组件只能接受一个子元素,为了遵照这种限制,创建一个<App>组件来渲染其他的应用将非常方便(将应用从router中分离对服务器端渲染也有重要意义,因为我们在服务器端转换到<MemoryRouter>时可以很快复用<App>

1
2
3
4
5
6
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'))

现在我们已经选择了router,我们可以开始渲染我们真正的应用了。

<App>

我们的应用定义在<App>组件中,为了简化<App>,我们将我们的应用分为两个部分,<Header>组件包含链接到其他页面的导航,<Main>组件包含其余的需要渲染的部分。

1
2
3
4
5
6
7
// this component will be rendered by our <___Router>
const App = () => (
<div>
<Header />
<Main />
</div>
)

Note: 你可以任意布局你的应用,分离routes和导航让你更加容易了解React Router是如何工作的。

我们先从渲染我们路由内容的<Main>组件开始。

Routes

<Route>组件是React Router的主要组成部分,如果你想要在路径符合的时候在任何地方渲染什么东西,你就应该创造一个<Route>元素。

Path

一个<Route>组件需要一个string类型的pathprop来指定路由需要匹配的路径。举例来说<Route path='/roster/'将匹配以/roster开始的路径,当当前的路径和path匹配时,route将会渲染React的元素。当路径不匹配的时候 ,路由不会渲染任何元素。

1
2
3
4
5
6
7
8
9
10
11
12
<Route path='/roster'/>
// when the pathname is '/', the path does not match
// when the pathname is '/roster' or '/roster/2', the path matches
// If you only want to match '/roster', then you need to use
// the "exact" prop. The following will match '/roster', but not
// '/roster/2'.
<Route exact path='/roster'/>
// You might find yourself adding the exact prop to most routes.
// In the future (i.e. v5), the exac t prop will likely be true by
// default. For more information on that, you can check out this
// GitHub issue:
// https://github.com/ReactTraining/react-router/issues/4958

Note: 在匹配路由的时候,React Router只会关心相对路径的部分,所以如下的URL

1
http://www.example.com/my-projects/one?extra=false

React Router只会尝试匹配/my-projects/one部分。

匹配路径

React Router使用path-to-regexp包来判断路径的pathprop是否匹配当前路径,它将path字符串转换成正则表达式与当前的路径进行匹配,关于path字符串更多的可选格式,可以查阅path-to-regexp文档

当路由与路径匹配的时候,一个具有以下match对象将会被作为prop传入

  • url - 当前路径与路由相匹配的部分
  • path - 路由的path
  • isExact - path === pathname
  • params - 一个包含着pathname的对象

Note: 目前,路由的路径必须是绝对路径。

创建我们自己的路由

<Route>s 可以在router中的任意位置被创建,不过一般来说将他们放到同一个地方渲染更加合理,你可以使用<Switch>组件来组合<Route>s,<Switch>将遍历children(路由),然后只匹配第一个符合的pathname

对于我们的网站来说,我们想要匹配的路径为:

  1. / - 主页
  2. /roster - 队伍名单
  3. /roster/:number - 队员的资料,number为队员的号码
  4. /schedule - 队伍的赛程表

为了匹配路径,我们需要创建带pathprop的<Route>元素

1
2
3
4
5
6
<Switch>
<Route exact path='/' component={Home}/>
{/* both /roster and /roster/:number begin with /roster */}
<Route path='/roster' component={Roster}/>
<Route path='/schedule' component={Schedule}/>
</Switch>

<Route>将会渲染什么

Routes可以接受三种prop来决定路径匹配时渲染的元素,只能给<Route>元素提供一种来定义要渲染的元素。

  1. <component> - 一个React组件,当一个带有componentprop的路由匹配的时候,路由将会返回prop提供的component类型的组件(通过React.createElement渲染)。
  2. render - 一个返回React元素的方法,与component类似,也是当路径匹配的时候会被调用。写成内联形式渲染和传递参数的时候非常方便。
  3. children - 一个返回React元素的方法。与前两种不同的是,这种方法总是会被渲染,无论路由与当前的路径是否匹配。
1
2
3
4
5
6
7
8
9
10
<Route path='/page' component={Page} />
const extraProps = { color: 'red' }
<Route path='/page' render={(props) => (
<Page {...props} data={extraProps}/>
)}/>
<Route path='/page' children={(props) => (
props.match
? <Page {...props}/>
: <EmptyPage {...props}/>
)}/>

一般来说,我们一般使用component或者renderchildren的使用场景不多,而且一般来说当路由不匹配的时候最好不要渲染任何东西。在我们的例子中,不需要向路由传递任何参数,所有我们使用<component>

<Route>渲染的元素将会带有一系列的props,有match对象,当前的location对象,还有history对象(由router创建)。

<Main>

现在我们已经确定了route的结构,我们只需要将他们实现即可。在我们的应用中,我们将会在<Main>组件中渲染<Switch><Route>,它们将会在<main>中渲染HTML元素。

1
2
3
4
5
6
7
8
9
10
import { Switch, Route } from 'react-router-dom'
const Main = () => (
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/roster' component={Roster}/>
<Route path='/schedule' component={Schedule}/>
</Switch>
</main>
)

Note: 主页的路由带有exactprop,这表明只有完全匹配pathname的时候才会匹配主页。

路由的嵌套

队员资料页的路由/roster/:number没有包含在<Switch>中,而是在<Roster>组件中,只要pathname由/roster开头,都将由<Roster>进行渲染。

<Roster>组件中我们将渲染两种路径:

  1. /roster - 只有当路径完全匹配/roster时会被渲染,我们要对该路径指定exact参数。
  2. /roster/:number - 这个路由使用一个路径参数来捕获/roster后面带的pathname的部分。
1
2
3
4
5
6
const Roster = () => (
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
)

将带有相同前缀的路由放在同一个组件中很方便,这样可以简化父组件并且让我们可以让我们在一个地方渲染所有带有相同前缀的组件。

举个例子,<Roster>可以为所有以/roster开头的路由渲染一个标题

1
2
3
4
5
6
7
8
9
const Roster = () => (
<div>
<h2>This is a roster page!</h2>
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
</div>
)

Path参数

有的时候我们想捕捉pathname中的多个参数,举例来说,在我们的球员资料路由中,我们通过添加了path参数来捕获了球员的号码。

:number部分代表在pathname中/roster/后面的内容将会被储存在match.params.number。举例来说,一个为/roster/6的pathname将会生成一个如下的params对象。

1
{ number: '6' } // note that the captured value is a string

<Player>组件使用props.match.params对象来决定应该渲染哪个球员的资料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// an API that returns a player object
import PlayerAPI from './PlayerAPI'
const Player = (props) => {
const player = PlayerAPI.get(
parseInt(props.match.params.number, 10)
)
if (!player) {
return <div>Sorry, but the player was not found</div>
}
return (
<div>
<h1>{player.name} (#{player.number})</h1>
<h2>{player.position}</h2>
</div>
)

关于path参数可以查阅path-to-regexp文档

<Player>组件旁,还有一个<FullRoster><Schedule><Home>组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const FullRoster = () => (
<div>
<ul>
{
PlayerAPI.all().map(p => (
<li key={p.number}>
<Link to={`/roster/${p.number}`}>{p.name}</Link>
</li>
))
}
</ul>
</div>
)
const Schedule = () => (
<div>
<ul>
<li>6/5 @ Evergreens</li>
<li>6/8 vs Kickers</li>
<li>6/14 @ United</li>
</ul>
</div>
)
const Home = () => (
<div>
<h1>Welcome to the Tornadoes Website!</h1>
</div>
)

最后,我们的网站需要在页面之间导航,如果我们使用<a标签导航的话,将会载入一整个新的页面。React Router提供了一个<Link>组件来阻止其发生,当点击<Link> 时,URL将会更新,页面也会在不载入整个新页面的情况下渲染内容。

1
2
3
4
5
6
7
8
9
10
11
12
import { Link } from 'react-router-dom'
const Header = () => (
<header>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/roster'>Roster</Link></li>
<li><Link to='/schedule'>Schedule</Link></li>
</ul>
</nav>
</header>
)

<Link>s使用to prop来决定导航的目标,可以是一个字符串,或者是一个location对象(包含pathname, search, hashstate属性)。当只是一个字符串的时候,将会被转化为一个location对象

1
<Link to={{ pathname: '/roster/7' }}>Player #7</Link>

Note: 目前,链接的pathname必须是绝对路径。

例子

两个在线的例子:

  1. CodeSandbox
  2. CodePen.

Notes!

[1] locations是包含描述URL不同部分的参数的对象

1
2
// a basic location object
{ pathname: '/', search: '', hash: '', key: 'abc123' state: {} }

[2] 可以一个无路径的<Route>,这个路由将会匹配所有路径,这样可以很方便的访问存储在context上的对象和方法。

[3] 当使用children prop时,即使在路径不匹配的时候也会渲染。

[4] 相对的<Link>s更加复杂,他们因为可能需要父组件的match对象,而不是当前的URL。

[5] 这是个基本的无状态组件,componentrender的区别是,component会使用React.createElement来创建一个元素,render使用函数来生成组件,如果你想创建一个需要传递prop的组件,那么render会比component来的方便。

1
2
3
4
<Route path='/one' component={One}/>
// React.createElement(props.component)
<Route path='/two' render={() => <Two />}/>
// props.render()

[6] <Route><Switch>组件都可以接受一个location prop,这可以让他们被一个不同的location匹配到,而不仅仅是他们实际的location(当前的URL)

[7] react-router也可以传递staticContext这个prop,但是只在使用服务端渲染的时候有效。

CATALOG
  1. 1. 简明React Router v4教程
    1. 1.1. 代码
    2. 1.2. 安装
    3. 1.3. Router
      1. 1.3.1. History
      2. 1.3.2. 渲染一个
    4. 1.4.
    5. 1.5. Routes
      1. 1.5.1. Path
      2. 1.5.2. 匹配路径
      3. 1.5.3. 创建我们自己的路由
      4. 1.5.4. 将会渲染什么
      5. 1.5.5.
      6. 1.5.6. 路由的嵌套
      7. 1.5.7. Path参数
      8. 1.5.8. Links
    6. 1.6. 例子
    7. 1.7. Notes!