[譯]簡明React Router v4教程

『 文章首發於 GitHub Blogjavascript

原文地址:A Simple React Router v4 Tutorialjava

React Router v4 是一個徹底使用 React 重寫的流行的 React 包,以前版本的 React Router 版本配置是使用僞組件也很晦澀難懂。如今 v4 版本的 React Router,全部的東西都 「僅僅是組件」。react

在這個教程中,咱們將創建一個本地的 "運動隊" 頁面,咱們將完成全部的基本需求來創建咱們的網站和路由,這包括:git

  1. 選擇 router
  2. 建立 routes
  3. 在路由之間經過連接進行導航。

代碼

Edit A Simple React Router v4 Tutorial

安裝

React Router 如今已經被劃分紅了三個包:react-routerreact-router-domreact-router-nativegithub

你不該該直接安裝 react-router,這個包爲 React Router 應用提供了核心的路由組件和函數,另外兩個包提供了特定環境的組件(瀏覽器和 react-native 對應的平臺),不過他們也是將 react-router 導出的模塊再次導出。正則表達式

你應該選擇這兩個中適應你開發環境的包,咱們須要構建一個網站(在瀏覽器中運行),因此咱們要安裝 react-router-domshell

npm install --save react-router-dom
複製代碼

Router

當開始一個新項目時,你應該決定要使用哪一種 router。對於在瀏覽器中運行的項目,咱們能夠選擇 <BrowserRouter<HashRouter> 組件,<BrowserRouter> 應該用在服務器處理動態請求的項目中(知道如何處理任意的URI),<HashRouter> 用來處理靜態頁面(只能響應請求已知文件的請求)。npm

一般來講更推薦使用 <BrowserRouter>,但是若是服務器只處理靜態頁面的請求,那麼使用 <HashRouter> 也是一個足夠的解決方案。react-native

對於咱們的項目,咱們假設全部的頁面都是由服務器動態生成的,因此咱們的 router 組件選擇 <BrowserRouter>瀏覽器

History

每一個 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> 組件開始。

Routes

<Route> 組件是 React Router 的主要組成部分,若是你想要在路徑符合的時候在任何地方渲染什麼東西,你就應該創造一個 <Route> 元素。

Path

一個 <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 - 一個包含着 pathnamepath-to-regexp 捕獲的對象

**Note: **目前,路由的路徑必須是絕對路徑 [4]。

建立咱們本身的路由

<Route>s 能夠在router中的任意位置被建立,不過通常來講將他們放到同一個地方渲染更加合理,你可使用 <Switch> 組件來組合 <Route>s,<Switch>將遍歷它的 children 元素(路由),而後只匹配第一個符合的 pathname

對於咱們的網站來講,咱們想要匹配的路徑爲:

  1. / - 主頁
  2. /roster - 隊伍名單
  3. /roster/:number - 隊員的資料,使用球員的球衣號碼來區分
  4. /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> 元素提供一種來定義要渲染的內容。

  1. <component> - 一個 React 組件,當一個帶有 component prop 的路由匹配的時候,路由將會返回 prop 提供的 component 類型的組件(經過 React.createElement 渲染)。
  2. render - 一個返回 React 元素 [5] 的方法,與 component 相似,也是當路徑匹配的時候會被調用。寫成內聯形式渲染和傳遞參數的時候很是方便。
  3. 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 或者 renderchildren 的使用場景很少,並且通常來講當路由不匹配的時候最好不要渲染任何東西。在咱們的例子中,不須要向路由傳遞任何參數,全部咱們使用 <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> 組件中咱們將渲染兩種路徑:

  1. /roster - 只有當路徑徹底匹配 /roster 時會被渲染,咱們要對該路徑指定 exact 參數。
  2. /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>
)
複製代碼

Path 參數

有的時候咱們想捕捉 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>
)
複製代碼

Links

最後,咱們的網站須要在頁面之間導航,若是咱們使用 <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, hashstate 屬性)。當只是一個字符串的時候,將會被轉化爲一個 location 對象

<Link to={{ pathname: '/roster/7' }}>Player #7</Link>
複製代碼

**Note: **目前,連接的 pathname 必須是絕對路徑。

例子

兩個在線的例子:

  1. CodeSandbox
  2. CodePen.

Notes!

[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] 這是個基本的無狀態組件,componentrender 的區別是,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,可是隻在使用服務端渲染的時候有效。

相關文章
相關標籤/搜索