『 文章首发于 GitHub Blog 』javascript
原文地址:A Simple React Router v4 Tutorialjava
React Router v4 是一个彻底使用 React 重写的流行的 React 包,以前版本的 React Router 版本配置是使用伪组件也很晦涩难懂。如今 v4 版本的 React Router,全部的东西都 “仅仅是组件”。react
在这个教程中,咱们将创建一个本地的 "运动队" 页面,咱们将完成全部的基本需求来创建咱们的网站和路由,这包括:git
router
。routes
。React Router 如今已经被划分红了三个包:react-router
,react-router-dom
,react-router-native
。github
你不该该直接安装 react-router
,这个包为 React Router 应用提供了核心的路由组件和函数,另外两个包提供了特定环境的组件(浏览器和 react-native
对应的平台),不过他们也是将 react-router
导出的模块再次导出。正则表达式
你应该选择这两个中适应你开发环境的包,咱们须要构建一个网站(在浏览器中运行),因此咱们要安装 react-router-dom
shell
npm install --save react-router-dom
复制代码
当开始一个新项目时,你应该决定要使用哪一种 router
。对于在浏览器中运行的项目,咱们能够选择 <BrowserRouter
和 <HashRouter>
组件,<BrowserRouter>
应该用在服务器处理动态请求的项目中(知道如何处理任意的URI),<HashRouter>
用来处理静态页面(只能响应请求已知文件的请求)。npm
一般来讲更推荐使用 <BrowserRouter>
,但是若是服务器只处理静态页面的请求,那么使用 <HashRouter>
也是一个足够的解决方案。react-native
对于咱们的项目,咱们假设全部的页面都是由服务器动态生成的,因此咱们的 router
组件选择 <BrowserRouter>
。浏览器
每一个 router
都会建立一个 history
对象,用来保持对当前位置[1]的追踪还有在页面发生变化的时候从新渲染页面。React Router 提供的其余组件依赖在 context
上储存的 history
对象,因此他们必须在 router
对象的内部渲染。一个没有 router
祖先元素的 React Router 对象将没法正常工做,若是你想学习更多的关于 history
对象的知识,能够参照 这篇文章。
<Router>
Router 的组件只能接受一个子元素,为了遵守这种限制,建立一个 <App>
组件来渲染其余的应用将很是方便(将应用从 router 中分离对服务器端渲染也有重要意义,由于咱们在服务器端转换到 <MemoryRouter>
时能够很快复用 <App>
)
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter> <App /> </BrowserRouter>
), document.getElementById('root'))
复制代码
如今咱们已经选择了 router,咱们能够开始渲染咱们真正的应用了。
<App>
咱们的应用定义在 <App>
组件中,为了简化 <App>
,咱们将咱们的应用分为两个部分,<Header>
组件包含连接到其余页面的导航,<Main>
组件包含其他的须要渲染的部分。
// this component will be rendered by our <___Router>
const App = () => (
<div> <Header /> <Main /> </div>
)
复制代码
Note: 你能够任意布局你的应用,分离 routes 和导航让你更加容易了解 React Router 是如何工做的。
咱们先从渲染咱们路由内容的 <Main>
组件开始。
<Route>
组件是 React Router 的主要组成部分,若是你想要在路径符合的时候在任何地方渲染什么东西,你就应该创造一个 <Route>
元素。
一个 <Route>
组件须要一个 string 类型的 path
prop 来指定路由须要匹配的路径。举例来讲,<Route path='/roster/'
将匹配以 /roster
[2] 开始的路径,当当前的路径和 path
匹配时,route 将会渲染对应的 React 元素。当路径不匹配的时候 ,路由不会渲染任何元素 [3]。
<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
http://www.example.com/my-projects/one?extra=false
复制代码
React Router 只会尝试匹配 /my-projects/one
。
React Router使用 path-to-regexp
包来判断路径的 path
prop 是否匹配当前路径,它将 path
字符串转换成正则表达式与当前的路径进行匹配,关于 path
字符串更多的可选格式,能够查阅 path-to-regexp
文档。
当路由与路径匹配的时候,一个具备如下属性的 match
对象将会被做为 prop 传入
url
- 当前路径与路由相匹配的部分path
- 路由的path
isExact
- path === pathname
params
- 一个包含着 pathname
被 path-to-regexp
捕获的对象**Note: **目前,路由的路径必须是绝对路径 [4]。
<Route>
s 能够在router中的任意位置被建立,不过通常来讲将他们放到同一个地方渲染更加合理,你可使用 <Switch>
组件来组合 <Route>
s,<Switch>
将遍历它的 children
元素(路由),而后只匹配第一个符合的 pathname
。
对于咱们的网站来讲,咱们想要匹配的路径为:
/
- 主页/roster
- 队伍名单/roster/:number
- 队员的资料,使用球员的球衣号码来区分/schedule
- 队伍的赛程表为了匹配路径,咱们须要建立带 path
prop的 <Route>
元素
<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>
元素提供一种来定义要渲染的内容。
<component>
- 一个 React 组件,当一个带有 component
prop 的路由匹配的时候,路由将会返回 prop 提供的 component 类型的组件(经过 React.createElement
渲染)。render
- 一个返回 React 元素 [5] 的方法,与 component
相似,也是当路径匹配的时候会被调用。写成内联形式渲染和传递参数的时候很是方便。children
- 一个返回 React 元素的方法。与前两种不一样的是,这种方法老是会被渲染,不管路由与当前的路径是否匹配。<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
或者 render
,children
的使用场景很少,并且通常来讲当路由不匹配的时候最好不要渲染任何东西。在咱们的例子中,不须要向路由传递任何参数,全部咱们使用 <component>
。
由 <Route>
渲染的元素将会带有一系列的 props,有 match
对象,当前的 location
对象 [6],还有 history
对象(由 router 建立)[7]。
<Main>
如今咱们已经肯定了 route 的结构,咱们只须要将他们实现便可。在咱们的应用中,咱们将会在 <Main>
组件中渲染 <Switch>
和 <Route>
,它们将会在 <main>
中渲染 HTML 元素。
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: **主页的路由带有 exact
prop,这代表只有路由的 path 彻底匹配 pathname 的时候才会匹配主页。
队员资料页的路由 /roster/:number
是在 <Roster>
组件而没有包含在 <Switch>
中。可是,只要 pathname 由 /roster
开头,它就会被 <Roster>
组件渲染。
在 <Roster>
组件中咱们将渲染两种路径:
/roster
- 只有当路径彻底匹配 /roster
时会被渲染,咱们要对该路径指定 exact
参数。/roster/:number
- 这个路由使用一个路径参数来捕获 /roster
后面带的 pathname 的部分。const Roster = () => (
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
)
复制代码
将带有相同前缀的路由放在同一个组件中很方便,这样能够简化父组件而且让咱们可让咱们在一个地方渲染全部带有相同前缀的组件。
举个例子,<Roster>
能够为全部以 /roster
开头的路由渲染一个标题
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>
)
复制代码
有的时候咱们想捕捉 pathname 中的多个参数,举例来讲,在咱们的球员资料路由中,咱们能够经过向路由的 path
添加路径参数来捕获球员的号码。
:number
部分表明在pathname中 /roster/
后面的内容将会被储存在 match.params.number
。举例来讲,一个为 /roster/6
的 pathname 将会生成一个以下的params 对象。
{ number: '6' } // note that the captured value is a string
复制代码
<Player>
组件使用 props.match.params
对象来决定应该渲染哪一个球员的资料。
// 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>
组件。
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 将会更新,页面也会在不载入整个新页面的状况下渲染内容。
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
, hash
和 state
属性)。当只是一个字符串的时候,将会被转化为一个 location 对象
<Link to={{ pathname: '/roster/7' }}>Player #7</Link>
复制代码
**Note: **目前,连接的 pathname 必须是绝对路径。
两个在线的例子:
[1] locations 是包含描述 URL 不一样部分的参数的对象
// a basic location object
{ pathname: '/', search: '', hash: '', key: 'abc123' state: {} }
复制代码
[2] 能够一个无路径的 <Route>
,这个路由将会匹配全部路径,这样能够很方便的访问存储在 context
上的对象和方法。
[3] 当使用 children
prop 时,即便在路径不匹配的时候也会渲染。
[4] 让 <Route>
s 和 <Link>
s 接受相对路径的工做还未完成,相对的 <Link>
s 比看上去要复杂的多,由于它们须要父组件的 match
对象来工做,而不是当前的 URL。
[5] 这是个基本的无状态组件,component
和 render
的区别是,component
会使用 React.createElement
来建立一个元素,render
使用将组件视做一个函数。若是你想建立一个内联函数并传递给 component
,那么 render
会比 component
来的快得多。
<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] props 也能够传递 staticContext
这个 prop,可是只在使用服务端渲染的时候有效。