『 文章首發於 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,可是隻在使用服務端渲染的時候有效。