React Router教程
React項目的可用的路由庫是React-Router
,當然這也是官方支持的。它也分為:
- react-router 核心組件
- react-router-dom 應用於瀏覽器端的路由庫(單獨使用包含了react-router的核心部分)
- react-router-native 應用於native端的路由
以下教程我們都以Web端為主,所以所有的教程內容都是默認關於
react-router-dom
的介紹。
進行網站(將會運行在瀏覽器環境中)構建,我們應當安裝react-router-dom
。react-router-dom
暴露出react-router
中暴露的對象與方法,因此你只需要安裝並引用react-router-dom
即可。
Installation | 安裝
安裝:
yarn add react-router-dom
# 或者,不使用 yarn npm install react-router-dom
路由的基本概念
現在的React Router版本中已不需要路由配置,現在一切皆組件。
ReactRouter中提供了以下三大組件:
- Router是所有路由組件共用的底層接口組件,它是路由規則制定的最外層的容器。
- Route路由規則匹配,並顯示當前的規則對應的組件。
- Link路由跳轉的組件
當然每個組件下又會有幾種不同的子類組件實現。比如: Router組件就針對不同功能和平台對應用:
<BrowserRouter>
瀏覽器的路由組件<HashRouter>
URL格式為Hash路由組件<MemoryRouter>
內存路由組件<NativeRouter>
Native的路由組件<StaticRouter>
地址不改變的靜態路由組件
三大組件使用的關系:

如果說我們的應用程序是一座小城的話,那么Route就是一座座帶有門牌號的建築物,而Link就代表了到某個建築物的路線。有了路線和目的地,那么就缺一位老司機了,沒錯Router就是這個老司機。
第一個Demo
現在你可以復制任意的示例代碼,並粘貼到 src/App.js
。如下:
import React, { Component } from 'react'; import { HashRouter as Router, Link, Route } from 'react-router-dom'; import './App.css'; const Home = () => ( <div> <h2>Home</h2> </div> ) const About = () => ( <div> <h2>About</h2> </div> ) const Product = () => ( <div> <h2>Product</h2> </div> ) class App extends Component { render() { return ( <Router> <div className="App"> <Link to="/">Home</Link> <Link to="/About">About</Link> <Link to="/Product">Product</Link> <hr/> <Route path="/" exact component={Home}></Route> <Route path="/about" component={About}></Route> <Route path="/product" component={Product}></Route> </div> </Router> ); } } export default App;
Router組件
BrowserRouter組件
BrowserRouter
主要使用在瀏覽器中,也就是WEB應用中。它利用HTML5 的history API來同步URL和UI的變化。當我們點擊了程序中的一個鏈接之后,BrowserRouter
就會找出與這個URL
匹配的Route
,並將他們對應的組件渲染出來。 BrowserRouter
是用來管理我們的組件的,那么它當然要被放在最頂級的位置,而我們的應用程序的組件就作為它的一個子組件而存在。
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.body);
BrowserRouter
組件提供了四個屬性。
basename
: 字符串類型,路由器的默認根路徑forceRefresh
: 布爾類型,在導航的過程中整個頁面是否刷新getUserConfirmation
: 函數類型,當導航需要確認時執行的函數。默認是:window.confirm
keyLength
: 數字類型location.key
的長度。默認是 6
basename 屬性
當前位置的基准 URL。如果你的頁面部署在服務器的二級(子)目錄,你需要將 basename
設置到此子目錄。正確的 URL 格式是前面有一個前導斜杠,但不能有尾部斜杠。
例如:有時候我們的應用只是整個系統中的一個模塊,應用中的URL總是以 http://localhost/admin/ 開頭。這種情況下我們總不能每次定義Link和Route的時候都帶上admin吧?react-router已經考慮到了這種情況,所以為我們提供了一個basename屬性。為BrowserRouter設置了basename之后,Link中就可以省略掉admin了,而最后渲染出來的URL又會自動帶上admin。
<BrowserRouter basename="/admin"/> ... <Link to="/home"/> // 被渲染為 <a href="/admin/home"> ... </BrowserRouter>
getUserConfirmation: func
當導航需要確認時執行的函數。默認使用 window.confirm
。
// 使用默認的確認函數 const getConfirmation = (message, callback) => { const allowTransition = window.confirm(message) callback(allowTransition) } <BrowserRouter getUserConfirmation={getConfirmation}/>
forceRefresh: bool
當設置為 true
時,在導航的過程中整個頁面將會刷新。 只有當瀏覽器不支持 HTML5 的 history API 時,才設置為 true
。
const supportsHistory = 'pushState' in window.history <BrowserRouter forceRefresh={!supportsHistory}/>
keyLength: number
location.key
的長度。默認是 6。
<BrowserRouter keyLength={12}/>
children: node
渲染單一子組件(元素)。
HashRouter
HashRouter
使用 URL 的 hash (例如:window.location.hash
) 來保持 UI 和 URL 的同步。
注意: 使用 hash 的方式記錄導航歷史不支持
location.key
和location.state
。在以前的版本中,我們為這種行為提供了 shim,但是仍有一些問題我們無法解。任何依賴此行為的代碼或插件都將無法正常使用。由於該技術僅用於支持傳統的瀏覽器,因此在用於瀏覽器時可以使用<BrowserHistory>
代替。
跟BrowserRouter
類似,它也有:basename
、getUserConfirmation
、children
屬性,而且是一樣的。
hashType: string
window.location.hash
使用的 hash 類型。有如下幾種:
"slash"
- 后面跟一個斜杠,例如#/
和#/sunshine/lollipops
"noslash"
- 后面沒有斜杠,例如#
和#sunshine/lollipops
"hashbang"
- Google 風格的 "ajax crawlable",例如#!/
和#!/sunshine/lollipops
默認為 "slash"
。
MemoryRouter
主要用在ReactNative這種非瀏覽器的環境中,因此直接將URL的history保存在了內存中。 StaticRouter 主要用於服務端渲染。
Link組件
Link就像是一個個的路牌,為我們指明組件的位置。Link使用聲明式的方式為應用程序提供導航功能,定義的Link最終會被渲染成一個a標簽。Link使用to這個屬性來指明目標組件的路徑,可以直接使用一個字符串,也可以傳入一個對象。
import { Link } from 'react-router-dom' // 字符串參數 <Link to="/query">查詢</Link> // 對象參數 <Link to={{ pathname: '/query', search: '?key=name', hash: '#hash', state: { fromDashboard: true } }}>查詢</Link>
屬性: to
需要跳轉到的路徑(pathname)或地址(location)。
屬性:replace: bool
當設置為 true
時,點擊鏈接后將使用新地址替換掉訪問歷史記錄里面的原地址。
當設置為 false
時,點擊鏈接后將在原有訪問歷史記錄的基礎上添加一個新的紀錄。
默認為 false
。
<Link to="/courses" replace />
NavLink組件
NavLink是一個特殊版本的Link,可以使用activeClassName來設置Link被選中時被附加的class,使用activeStyle來配置被選中時應用的樣式。此外,還有一個exact屬性,此屬性要求location完全匹配才會附加class和style。這里說的匹配是指地址欄中的URl和這個Link的to指定的location相匹配。
// 選中后被添加class selected
<NavLink to={'/'} exact activeClassName='selected'>Home</NavLink> // 選中后被附加樣式 color:red <NavLink to={'/gallery'} activeStyle={{color:red}}>Gallery</NavLink>
activeClassName
默認值為active
屬性
- to 可以是字符串或者對象,同Link組件
- exact 布爾類型,完全匹配時才會被附件class和style
- activeStyle Object類型
- activeClassName 字符串類型
- strict: bool類型,當值為
true
時,在確定位置是否與當前 URL 匹配時,將考慮位置pathname
后的斜線。
Route組件
Route應該是react-route中最重要的組件了,它的作用是當location與Route的path匹配時渲染Route中的Component。如果有多個Route匹配,那么這些Route的Component都會被渲染。
與Link類似,Route也有一個exact屬性,作用也是要求location與Route的path絕對匹配。
// 當location形如 http://location/時,Home就會被渲染。 // 因為 "/" 會匹配所有的URL,所以這里設置一個exact來強制絕對匹配。 <Route exact path="/" component={Home}/> <Route path="/about" component={About}/>
Route的三種渲染方式
- component: 這是最常用也最容易理解的方式,給什么就渲染什么。
- render: render的類型是function,Route會渲染這個function的返回值。因此它的作用就是附加一些額外的邏輯。
<Route path="/home" render={() => { console.log('額外的邏輯'); return (<div>Home</div>); }/>
- children: 這是最特殊的渲染方式。
一、它同render類似,是一個function。不同的地方在於它會被傳入一個match參數來告訴你這個Route的path和location匹配上沒有。
二、第二個特殊的地方在於,即使path沒有匹配上,我們也可以將它渲染出來。秘訣就在於前面一點提到的match參數。我們可以根據這個參數來決定在匹配的時候渲染什么,不匹配的時候又渲染什么。
// 在匹配時,容器的calss是light,<Home />會被渲染 // 在不匹配時,容器的calss是dark,<About />會被渲染 <Route path='/home' children={({ match }) => ( <div className={match ? 'light' : 'dark'}> {match ? <Home/>:<About>} </div> )}/>
所有路由中指定的組件將被傳入以下三個 props 。
- match.
- location.
- history.
這里主要說下match.params.透過這個屬性,我們可以拿到從location中解析出來的參數。當然,如果想要接收參數,我們的Route的path也要使用特殊的寫法。
如下示例,三個Link是一個文章列表中三個鏈接,分別指向三篇id不同的文章。而Route用於渲染文章詳情頁。注意path='/p/:id' ,location中的對應的段會被解析為id=1 這樣的鍵值。最終這個鍵值會作為param的鍵值存在。Route中的組件可以使用this.props.match.params.id來獲取,示例中使用了結構賦值。
<Link to='/p/1' /> <Link to='/p/2' /> <Link to='/p/3' /> ...... <Route path='/p/:id' render={(match)=<h3>當前文章ID:{match.params.id}</h3>)} />
location
Location 是指你當前的位置,下一步打算去的位置,或是你之前所在的位置,形式大概就像這樣:
{
key: 'ac3df4', // 在使用 hashHistory 時,沒有 key pathname: '/somewhere' search: '?some=search-string', hash: '#howdy', state: { [userDefined]: true } }
你使用以下幾種方式來獲取 location 對象:
- 在 Route component 中,以
this.props.location
的方式獲取, - 在 Route render 中,以
({ location }) => ()
的方式獲取, - 在 Route children 中,以
({ location }) => ()
的方式獲取, - 在 withRouter 中,以
this.props.location
的方式獲取。
你也可以在 history.location
中獲取 location 對象,但是別那么寫,因為 history 是可變的。更多信息請參見 history 文檔。
location 對象不會發生改變,因此你可以在生命周期的鈎子函數中使用 location 對象來查看當前頁面的位置是否發生改變,這種技巧在獲取遠程數據以及使用動畫時非常有用。
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) { // 已經跳轉了! } }
通常情況下,你只需要給一個字符串當做 location ,但是,當你需要添加一些 location 的狀態時,你可以對象的形式使用 location 。並且當你需要多個 UI ,而這些 UI 取決於歷史時,例如彈出框(modal),使用location 對象會有很大幫助。
// 通常你只需要這樣使用 location <Link to="/somewhere"/> // 但是你同樣可以這么用 const location = { pathname: '/somewhere' state: { fromDashboard: true } } <Link to={location}/> <Redirect to={location}/> history.push(location) history.replace(location)
最后,你可以把 location 傳入一下組件:
- [Route]
- [Switch]
這樣做可以讓組件不使用路由狀態(router state)中的真實 location,因為我們有時候需要組件去渲染一個其他的 location 而不是本身所處的真實 location,比如使用動畫或是等待跳轉時。
history
本文檔中的「history」以及「history
對象」請參照 history
包中的內容。 History 是 React Router 的兩大重要依賴之一(除去 React 本身),在不同的 Javascript 環境中,history
以多種形式實現了對於 session 歷史的管理。
我們會經常使用以下術語:
- 「browser history」 - history 在 DOM 上的實現,經常使用於支持 HTML5 history API 的瀏覽器端。
- 「hash history」 - history 在 DOM 上的實現,經常使用於舊版本瀏覽器端。
- 「memory history」 - 一種存儲於內存的 history 實現,經常用於測試或是非 DOM 環境(例如 React Native)。
history
對象通常會具有以下屬性和方法:
length
-( number 類型)指的是 history 堆棧的數量。action
-( string 類型)指的是當前的動作(action),例如PUSH
,REPLACE
以及POP
。location
-( object類型)是指當前的位置(location),location 會具有如下屬性:pathname
-( string 類型)URL路徑。search
-( string 類型)URL中的查詢字符串(query string)。hash
-( string 類型)URL的 hash 分段。state
-( string 類型)是指 location 中的狀態,例如在push(path, state)
時,state會描述什么時候 location 被放置到堆棧中等信息。這個 state 只會出現在 browser history 和 memory history 的環境里。
push(path, [state])
-( function 類型)在 hisotry 堆棧頂加入一個新的條目。replace(path, [state])
-( function 類型)替換在 history 堆棧中的當前條目。go(n)
-( function 類型)將 history 對戰中的指針向前移動n
。goBack()
-( function 類型)等同於go(-1)
。goForward()
-( function 類型)等同於go(1)
。block(prompt)
-( function 類型)阻止跳轉,(請參照 history 文檔)
match
match
對象包含了 <Route path>
如何與URL匹配的信息。match
對象包含以下屬性:
params
-( object 類型)即路徑參數,通過解析URL中動態的部分獲得的鍵值對。isExact
- 當為true
時,整個URL都需要匹配。path
-( string 類型)用來做匹配的路徑格式。在需要嵌套<Route>
的時候用到。url
-( string 類型)URL匹配的部分,在需要嵌套<Link>
的時候會用到。
你可以在以下地方獲取 match
對象:
- 在 Route component 中,以
this.props.match
方式。 - 在 Route render中,以
({ match }) => ()
方式。 - 在 Route children中,以
({ match }) => ()
方式
Redirect組件
當這個組件被渲染是,location會被重寫為Redirect的to指定的新location。它的一個用途是登錄重定向,比如在用戶點了登錄並驗證通過之后,將頁面跳轉到個人主頁。
<Redirect to="/new"/>
Switch組件
渲染匹配地址(location)的第一個 <Route>
或者<Redirect>
這與只使用一堆<Route>
有什么不同?
<Switch>
的獨特之處是獨它僅僅渲染一個路由。相反地,每一個包含匹配地址(location)的<Route>
都會被渲染。思考下面的代碼:
<Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/>
如果現在的URL是 /about
,那么 <About>
, <User>
, 還有 <NoMatch>
都會被渲染,因為它們都與路徑(path)匹配。這種設計,允許我們以多種方式將多個 <Route>
組合到我們的應用程序中,例如側欄(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶爾我們只想選擇一個<Route>
來渲染。如果我們現在處於 /about
,我們也不希望匹配 /:user
(或者顯示我們的 "404" 頁面 )。以下是使用 Switch
的方法來實現:
import { Switch, Route } from 'react-router'
<Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </Switch>
現在,如果我們處於 /about
, <Switch>
將開始尋找匹配的 <Route>
。 <Route path="/about"/>
將被匹配, <Switch>
將停止尋找匹配並渲染<About>
。 同樣,如果我們處於 /michael
, <User>
將被渲染。
這對於過渡動畫也是起作用的,因為匹配的 <Route>
在與前一個相同的位置被渲染。
<Fade>
<Switch> {/* there will only ever be one child here */} {/* 這里只會有一個子節點 */} <Route/> <Route/> </Switch> </Fade> <Fade> <Route/> <Route/> {/* there will always be two children here, one might render null though, making transitions a bit more cumbersome to work out */} {/* 這里總是有兩個子節點, 一個可能會渲染為null, 使計算過渡增加了一點麻煩 */} </Fade>