轉(zhuǎn)載自 阮一峰React Router 使用教程
參考
React router 中文文檔
React router 例子
一、基本用法
使用時(shí),路由器Router就是React的一個(gè)組件。
import { Router } from 'react-router';
render(<Router/>, document.getElementById('app'));
Router組件本身只是一個(gè)容器,真正的路由要通過Route組件定義。
import { Router, Route, hashHistory } from 'react-router';
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'));
上面代碼中,用戶訪問根路由/(比如http://www.example.com/
),組件APP就會(huì)加載到document.getElementById('app')
。
你可能還注意到,Router組件有一個(gè)參數(shù)history,它的值hashHistory表示,路由的切換由URL的hash變化決定,即URL的#部分發(fā)生變化。舉例來說,用戶訪問http://www.example.com/
,實(shí)際會(huì)看到的是http://www.example.com/#/
。
Route組件定義了URL路徑與組件的對(duì)應(yīng)關(guān)系。你可以同時(shí)使用多個(gè)Route組件。
<Router history={hashHistory}>
<Route path="/" component={App}/>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
上面代碼中,用戶訪問/repos(比如http://localhost:8080/#/repos
)時(shí),加載Repos組件;訪問/about(http://localhost:8080/#/about
)時(shí),加載About組件。
二、嵌套路由
Route組件還可以嵌套。
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
上面代碼中,用戶訪問/repos時(shí),會(huì)先加載App組件,然后在它的內(nèi)部再加載Repos組件。
<App>
<Repos/>
</App>
App組件要寫成下面的樣子。
export default React.createClass({
render() {
return <div>
{this.props.children}
</div>
}
})
上面代碼中,App組件的this.props.children屬性就是子組件。
子路由也可以不寫在Router組件里面,單獨(dú)傳入Router組件的routes屬性。
let routes = <Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>;
<Router routes={routes} history={browserHistory}/>
三、 path 屬性
Route
組件的path
屬性指定路由的匹配規(guī)則。這個(gè)屬性是可以省略的,這樣的話,不管路徑是否匹配,總是會(huì)加載指定組件。
請看下面的例子。
<Route path="inbox" component={Inbox}> <Route path="messages/:id" component={Message} /> </Route>
上面代碼中,當(dāng)用戶訪問/inbox/messages/:id
時(shí),會(huì)加載下面的組件。
<Inbox> <Message/> </Inbox>
如果省略外層Route
的path
參數(shù),寫成下面的樣子。
<Route component={Inbox}> <Route path="inbox/messages/:id" component={Message} /> </Route>
現(xiàn)在用戶訪問/inbox/messages/:id
時(shí),組件加載還是原來的樣子。
<Inbox> <Message/> </Inbox>
四、通配符
path
屬性可以使用通配符。
<Route path="/hello/:name"> // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/hello(/:name)"> // 匹配 /hello // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg // 匹配 /files/hello.html <Route path="/files/*"> // 匹配 /files/ // 匹配 /files/a // 匹配 /files/a/b <Route path="/**/*.jpg"> // 匹配 /files/hello.jpg // 匹配 /files/path/to/file.jpg
通配符的規(guī)則如下。
(1)
:paramName
:paramName
匹配URL的一個(gè)部分,直到遇到下一個(gè)/
、?
、#
為止。這個(gè)路徑參數(shù)可以通過this.props.params.paramName
取出。(2)
()
()
表示URL的這個(gè)部分是可選的。(3)
*
*
匹配任意字符,直到模式里面的下一個(gè)字符為止。匹配方式是非貪婪模式。(4)
**
**
匹配任意字符,直到下一個(gè)/
、?
、#
為止。匹配方式是貪婪模式。
path
屬性也可以使用相對(duì)路徑(不以/
開頭),匹配時(shí)就會(huì)相對(duì)于父組件的路徑,可以參考上一節(jié)的例子。嵌套路由如果想擺脫這個(gè)規(guī)則,可以使用絕對(duì)路由。
路由匹配規(guī)則是從上到下執(zhí)行,一旦發(fā)現(xiàn)匹配,就不再其余的規(guī)則了。
<Route path="/comments" ... /> <Route path="/comments" ... />
上面代碼中,路徑/comments
同時(shí)匹配兩個(gè)規(guī)則,第二個(gè)規(guī)則不會(huì)生效。
設(shè)置路徑參數(shù)時(shí),需要特別小心這一點(diǎn)。
<Router> <Route path="/:userName/:id" component={UserPage}/> <Route path="/about/me" component={About}/> </Router>
上面代碼中,用戶訪問/about/me
時(shí),不會(huì)觸發(fā)第二個(gè)路由規(guī)則,因?yàn)樗鼤?huì)匹配/:userName/:id
這個(gè)規(guī)則。因此,帶參數(shù)的路徑一般要寫在路由規(guī)則的底部。
此外,URL的查詢字符串/foo?bar=baz
,可以用this.props.location.query.bar
獲取。
五、IndexRoute 組件
下面的例子,你會(huì)不會(huì)覺得有一點(diǎn)問題?
<Router> <Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router>
上面代碼中,訪問根路徑/
,不會(huì)加載任何子組件。也就是說,App
組件的this.props.children
,這時(shí)是undefined
。
因此,通常會(huì)采用{this.props.children || <Home/>}
這樣的寫法。這時(shí),Home
明明是Accounts
和Statements
的同級(jí)組件,卻沒有寫在Route
中。
IndexRoute
就是解決這個(gè)問題,顯式指定Home
是根路由的子組件,即指定默認(rèn)情況下加載的子組件。你可以把IndexRoute
想象成某個(gè)路徑的index.html
。
<Router> <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router>
現(xiàn)在,用戶訪問/
的時(shí)候,加載的組件結(jié)構(gòu)如下。
<App> <Home/> </App>
這種組件結(jié)構(gòu)就很清晰了:App
只包含下級(jí)組件的共有元素,本身的展示內(nèi)容則由Home
組件定義。這樣有利于代碼分離,也有利于使用React Router提供的各種API。
注意,IndexRoute
組件沒有路徑參數(shù)path
。
六、Redirect 組件
<Redirect>
組件用于路由的跳轉(zhuǎn),即用戶訪問一個(gè)路由,會(huì)自動(dòng)跳轉(zhuǎn)到另一個(gè)路由。
<Route path="inbox" component={Inbox}> {/* 從 /inbox/messages/:id 跳轉(zhuǎn)到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route>
現(xiàn)在訪問/inbox/messages/5
,會(huì)自動(dòng)跳轉(zhuǎn)到/messages/5
。
七、IndexRedirect 組件
IndexRedirect
組件用于訪問根路由的時(shí)候,將用戶重定向到某個(gè)子組件。
<Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /> </Route>
上面代碼中,用戶訪問根路徑時(shí),將自動(dòng)重定向到子組件welcome
。
八、Link
Link
組件用于取代<a>
元素,生成一個(gè)鏈接,允許用戶點(diǎn)擊后跳轉(zhuǎn)到另一個(gè)路由。它基本上就是<a>
元素的React 版本,可以接收Router
的狀態(tài)。
render() { return <div> <ul role="nav"> <li><Link to="/about">About</Link></li> <li><Link to="/repos">Repos</Link></li> </ul> </div> }
如果希望當(dāng)前的路由與其他路由有不同樣式,這時(shí)可以使用Link
組件的activeStyle
屬性。
<Link to="/about" activeStyle={{color: 'red'}}>About</Link> <Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>
上面代碼中,當(dāng)前頁面的鏈接會(huì)紅色顯示。
另一種做法是,使用activeClassName
指定當(dāng)前路由的Class
。
<Link to="/about" activeClassName="active">About</Link> <Link to="/repos" activeClassName="active">Repos</Link>
上面代碼中,當(dāng)前頁面的鏈接的class
會(huì)包含active
。
在Router
組件之外,導(dǎo)航到路由頁面,可以使用瀏覽器的History API,像下面這樣寫。
import { browserHistory } from 'react-router'; browserHistory.push('/some/path');
九、IndexLink
如果鏈接到根路由/
,不要使用Link
組件,而要使用IndexLink
組件。
這是因?yàn)閷?duì)于根路由來說,activeStyle
和activeClassName
會(huì)失效,或者說總是生效,因?yàn)?code>/會(huì)匹配任何子路由。而IndexLink
組件會(huì)使用路徑的精確匹配。
<IndexLink to="/" activeClassName="active"> Home </IndexLink>
上面代碼中,根路由只會(huì)在精確匹配時(shí),才具有activeClassName
。
另一種方法是使用Link
組件的onlyActiveOnIndex
屬性,也能達(dá)到同樣效果。
<Link to="/" activeClassName="active" onlyActiveOnIndex={true}> Home </Link>
實(shí)際上,IndexLink
就是對(duì)Link
組件的onlyActiveOnIndex
屬性的包裝。
十、history 屬性
Router
組件的history
屬性,用來監(jiān)聽瀏覽器地址欄的變化,并將URL解析成一個(gè)地址對(duì)象,供 React Router 匹配。
history
屬性,一共可以設(shè)置三種值。
- browserHistory
- hashHistory
- createMemoryHistory
如果設(shè)為hashHistory
,路由將通過URL的hash部分(#
)切換,URL的形式類似example.com/#/some/path
。
import { hashHistory } from 'react-router' render( <Router history={hashHistory} routes={routes} />, document.getElementById('app') )
如果設(shè)為browserHistory
,瀏覽器的路由就不再通過Hash
完成了,而顯示正常的路徑example.com/some/path
,背后調(diào)用的是瀏覽器的History API。
import { browserHistory } from 'react-router' render( <Router history={browserHistory} routes={routes} />, document.getElementById('app') )
但是,這種情況需要對(duì)服務(wù)器改造。否則用戶直接向服務(wù)器請求某個(gè)子路由,會(huì)顯示網(wǎng)頁找不到的404錯(cuò)誤。
如果開發(fā)服務(wù)器使用的是webpack-dev-server
,加上--history-api-fallback
參數(shù)就可以了。
$ webpack-dev-server --inline --content-base . --history-api-fallback
createMemoryHistory
主要用于服務(wù)器渲染。它創(chuàng)建一個(gè)內(nèi)存中的history
對(duì)象,不與瀏覽器URL互動(dòng)。
const history = createMemoryHistory(location)
十一、表單處理
Link組件用于正常的用戶點(diǎn)擊跳轉(zhuǎn),但是有時(shí)還需要表單跳轉(zhuǎn)、點(diǎn)擊按鈕跳轉(zhuǎn)等操作。這些情況怎么跟React Router對(duì)接呢?
下面是一個(gè)表單。
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="userName"/>
<input type="text" placeholder="repo"/>
<button type="submit">Go</button>
</form>
第一種方法是使用browserHistory.push
import { browserHistory } from 'react-router'
// ...
handleSubmit(event) {
event.preventDefault()
const userName = event.target.elements[0].value
const repo = event.target.elements[1].value
const path = `/repos/${userName}/${repo}`
browserHistory.push(path)
},
第二種方法是使用context對(duì)象。
export default React.createClass({
// ask for `router` from context
contextTypes: {
router: React.PropTypes.object
},
handleSubmit(event) {
// ...
this.context.router.push(path)
},
})
十二、路由的鉤子
每個(gè)路由都有Enter和Leave鉤子,用戶進(jìn)入或離開該路由時(shí)觸發(fā)。
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
上面的代碼中,如果用戶離開/messages/:id,進(jìn)入/about時(shí),會(huì)依次觸發(fā)以下的鉤子。
/messages/:id的onLeave
/inbox的onLeave
/about的onEnter
下面是一個(gè)例子,使用onEnter鉤子替代<Redirect>組件。
<Route path="inbox" component={Inbox}>
<Route
path="messages/:id"
onEnter={
({params}, replace) => replace(`/messages/${params.id}`)
}
/>
</Route>
onEnter鉤子還可以用來做認(rèn)證。
const requireAuth = (nextState, replace) => {
if (!auth.isAdmin()) {
// Redirect to Home page if not an Admin
replace({ pathname: '/' })
}
}
export const AdminRoutes = () => {
return (
<Route path="/admin" component={Admin} onEnter={requireAuth} />
)
}
下面是一個(gè)高級(jí)應(yīng)用,當(dāng)用戶離開一個(gè)路徑的時(shí)候,跳出一個(gè)提示框,要求用戶確認(rèn)是否離開。
const Home = withRouter(
React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
routerWillLeave(nextLocation) {
// 返回 false 會(huì)繼續(xù)停留當(dāng)前頁面,
// 否則,返回一個(gè)字符串,會(huì)顯示給用戶,讓其自己決定
if (!this.state.isSaved)
return '確認(rèn)要離開?';
},
})
)
上面代碼中,setRouteLeaveHook方法為Leave鉤子指定routerWillLeave函數(shù)。該方法如果返回false,將阻止路由的切換,否則就返回一個(gè)字符串,提示用戶決定是否要切換。