[轉] React Router 使用教程


你會發現,它不是一個庫,也不是一個框架,而是一個龐大的體系。想要發揮它的威力,整個技術棧都要配合它改造。你要學習一整套解決方案,從后端到前端,都是全新的做法。

舉例來說,React 不使用 HTML,而使用 JSX 。它打算拋棄 DOM,要求開發者不要使用任何 DOM 方法。它甚至還拋棄了 SQL ,自己發明了一套查詢語言 GraphQL 。當然,這些你都可以不用,React 照樣運行,但是就發揮不出它的最大威力。

這樣說吧,你只要用了 React,就會發現合理的選擇就是,采用它的整個技術棧。

本文介紹 React 體系的一個重要部分:路由庫React-Router。它是官方維護的,事實上也是唯一可選的路由庫。它通過管理 URL,實現組件的切換和狀態的變化,開發復雜的應用幾乎肯定會用到。

本文針對初學者,盡量寫得簡潔易懂。預備知識是 React 的基本用法,可以參考我寫的《React 入門實例教程》

另外,我沒有准備示例庫,因為官方的示例庫非常棒,由淺入深,分成14步,每一步都有詳細的代碼解釋。我強烈建議你先跟着做一遍,然后再看下面的API講解。

([說明] 本文寫作時,React-router 是 2.x 版,本文的內容只適合這個版本,與最新的 4.x 版不兼容。目前,官方同時維護 2.x 和 4.x 兩個版本,所以前者依然可以用在項目中。2017年3月)

一、基本用法

React Router 安裝命令如下。


$ npm install -S react-router 

使用時,路由器Router就是React的一個組件。


import { Router } from 'react-router'; render(<Router/>, document.getElementById('app')); 

Router組件本身只是一個容器,真正的路由要通過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就會加載到document.getElementById('app')

你可能還注意到,Router組件有一個參數history,它的值hashHistory表示,路由的切換由URL的hash變化決定,即URL的#部分發生變化。舉例來說,用戶訪問http://www.example.com/,實際會看到的是http://www.example.com/#/

Route組件定義了URL路徑與組件的對應關系。你可以同時使用多個Route組件。


<Router history={hashHistory}>
  <Route path="/" component={App}/>
  <Route path="/repos" component={Repos}/>
  <Route path="/about" component={About}/>
</Router> 

上面代碼中,用戶訪問/repos(比如http://localhost:8080/#/repos)時,加載Repos組件;訪問/abouthttp://localhost:8080/#/about)時,加載About組件。

二、嵌套路由

Route組件還可以嵌套。


<Router history={hashHistory}> <Route path="/" component={App}> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Route> </Router> 

上面代碼中,用戶訪問/repos時,會先加載App組件,然后在它的內部再加載Repos組件。


<App> <Repos/> </App> 

App組件要寫成下面的樣子。


export default React.createClass({ render() { return <div> {this.props.children} </div> } }) 

上面代碼中,App組件的this.props.children屬性就是子組件。

子路由也可以不寫在Router組件里面,單獨傳入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屬性指定路由的匹配規則。這個屬性是可以省略的,這樣的話,不管路徑是否匹配,總是會加載指定組件。

請看下面的例子。


<Route path="inbox" component={Inbox}>
   <Route path="messages/:id" component={Message} />
</Route> 

上面代碼中,當用戶訪問/inbox/messages/:id時,會加載下面的組件。


<Inbox> <Message/> </Inbox> 

如果省略外層Routepath參數,寫成下面的樣子。


<Route component={Inbox}>
  <Route path="inbox/messages/:id" component={Message} />
</Route> 

現在用戶訪問/inbox/messages/:id時,組件加載還是原來的樣子。


<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 

通配符的規則如下。

(1):paramName

:paramName匹配URL的一個部分,直到遇到下一個/?#為止。這個路徑參數可以通過this.props.params.paramName取出。

(2)()

()表示URL的這個部分是可選的。

(3)*

*匹配任意字符,直到模式里面的下一個字符為止。匹配方式是非貪婪模式。

(4) **

** 匹配任意字符,直到下一個/?#為止。匹配方式是貪婪模式。

path屬性也可以使用相對路徑(不以/開頭),匹配時就會相對於父組件的路徑,可以參考上一節的例子。嵌套路由如果想擺脫這個規則,可以使用絕對路由。

路由匹配規則是從上到下執行,一旦發現匹配,就不再其余的規則了。


<Route path="/comments" ... />
<Route path="/comments" ... />

上面代碼中,路徑/comments同時匹配兩個規則,第二個規則不會生效。

設置路徑參數時,需要特別小心這一點。


<Router> <Route path="/:userName/:id" component={UserPage}/> <Route path="/about/me" component={About}/> </Router> 

上面代碼中,用戶訪問/about/me時,不會觸發第二個路由規則,因為它會匹配/:userName/:id這個規則。因此,帶參數的路徑一般要寫在路由規則的底部。

此外,URL的查詢字符串/foo?bar=baz,可以用this.props.location.query.bar獲取。

五、IndexRoute 組件

下面的例子,你會不會覺得有一點問題?


<Router> <Route path="/" component={App}> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router> 

上面代碼中,訪問根路徑/,不會加載任何子組件。也就是說,App組件的this.props.children,這時是undefined

因此,通常會采用{this.props.children || <Home/>}這樣的寫法。這時,Home明明是AccountsStatements的同級組件,卻沒有寫在Route中。

IndexRoute就是解決這個問題,顯式指定Home是根路由的子組件,即指定默認情況下加載的子組件。你可以把IndexRoute想象成某個路徑的index.html


<Router> <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router> 

現在,用戶訪問/的時候,加載的組件結構如下。


<App> <Home/> </App> 

這種組件結構就很清晰了:App只包含下級組件的共有元素,本身的展示內容則由Home組件定義。這樣有利於代碼分離,也有利於使用React Router提供的各種API。

注意,IndexRoute組件沒有路徑參數path

六、Redirect 組件

<Redirect>組件用於路由的跳轉,即用戶訪問一個路由,會自動跳轉到另一個路由。


<Route path="inbox" component={Inbox}> {/* 從 /inbox/messages/:id 跳轉到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route> 

現在訪問/inbox/messages/5,會自動跳轉到/messages/5

七、IndexRedirect 組件

IndexRedirect組件用於訪問根路由的時候,將用戶重定向到某個子組件。


<Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /> </Route> 

上面代碼中,用戶訪問根路徑時,將自動重定向到子組件welcome

八、Link

Link組件用於取代<a>元素,生成一個鏈接,允許用戶點擊后跳轉到另一個路由。它基本上就是<a>元素的React 版本,可以接收Router的狀態。


render() { return <div> <ul role="nav"> <li><Link to="/about">About</Link></li> <li><Link to="/repos">Repos</Link></li> </ul> </div> } 

如果希望當前的路由與其他路由有不同樣式,這時可以使用Link組件的activeStyle屬性。


<Link to="/about" activeStyle={{color: 'red'}}>About</Link> <Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link> 

上面代碼中,當前頁面的鏈接會紅色顯示。

另一種做法是,使用activeClassName指定當前路由的Class


<Link to="/about" activeClassName="active">About</Link> <Link to="/repos" activeClassName="active">Repos</Link> 

上面代碼中,當前頁面的鏈接的class會包含active

Router組件之外,導航到路由頁面,可以使用瀏覽器的History API,像下面這樣寫。


import { browserHistory } from 'react-router'; browserHistory.push('/some/path'); 

九、IndexLink

如果鏈接到根路由/,不要使用Link組件,而要使用IndexLink組件。

這是因為對於根路由來說,activeStyleactiveClassName會失效,或者說總是生效,因為/會匹配任何子路由。而IndexLink組件會使用路徑的精確匹配。


<IndexLink to="/" activeClassName="active"> Home </IndexLink> 

上面代碼中,根路由只會在精確匹配時,才具有activeClassName

另一種方法是使用Link組件的onlyActiveOnIndex屬性,也能達到同樣效果。


<Link to="/" activeClassName="active" onlyActiveOnIndex={true}>
  Home
</Link> 

實際上,IndexLink就是對Link組件的onlyActiveOnIndex屬性的包裝。

十、histroy 屬性

Router組件的history屬性,用來監聽瀏覽器地址欄的變化,並將URL解析成一個地址對象,供 React Router 匹配。

history屬性,一共可以設置三種值。

  • browserHistory
  • hashHistory
  • createMemoryHistory

如果設為hashHistory,路由將通過URL的hash部分(#)切換,URL的形式類似example.com/#/some/path


import { hashHistory } from 'react-router' render( <Router history={hashHistory} routes={routes} />, document.getElementById('app') ) 

如果設為browserHistory,瀏覽器的路由就不再通過Hash完成了,而顯示正常的路徑example.com/some/path,背后調用的是瀏覽器的History API。


import { browserHistory } from 'react-router' render( <Router history={browserHistory} routes={routes} />, document.getElementById('app') ) 

但是,這種情況需要對服務器改造。否則用戶直接向服務器請求某個子路由,會顯示網頁找不到的404錯誤。

如果開發服務器使用的是webpack-dev-server,加上--history-api-fallback參數就可以了。


$ webpack-dev-server --inline --content-base . --history-api-fallback 

createMemoryHistory主要用於服務器渲染。它創建一個內存中的history對象,不與瀏覽器URL互動。


const history = createMemoryHistory(location) 

十一、表單處理

Link組件用於正常的用戶點擊跳轉,但是有時還需要表單跳轉、點擊按鈕跳轉等操作。這些情況怎么跟React Router對接呢?

下面是一個表單。


<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對象。


export default React.createClass({  // ask for `router` from context contextTypes: { router: React.PropTypes.object }, handleSubmit(event) {  // ... this.context.router.push(path) }, }) 

十二、路由的鈎子

每個路由都有EnterLeave鈎子,用戶進入或離開該路由時觸發。


<Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Redirect from="messages/:id" to="/messages/:id" /> </Route> 

上面的代碼中,如果用戶離開/messages/:id,進入/about時,會依次觸發以下的鈎子。

  • /messages/:idonLeave
  • /inboxonLeave
  • /aboutonEnter

下面是一個例子,使用onEnter鈎子替代<Redirect>組件。


<Route path="inbox" component={Inbox}>
  <Route
    path="messages/:id"
    onEnter={
      ({params}, replace) => replace(`/messages/${params.id}`)
    } 
  />
</Route> 

onEnter鈎子還可以用來做認證。


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} />
  )
}

下面是一個高級應用,當用戶離開一個路徑的時候,跳出一個提示框,要求用戶確認是否離開。


const Home = withRouter( React.createClass({ componentDidMount() { this.props.router.setRouteLeaveHook( this.props.route, this.routerWillLeave ) }, routerWillLeave(nextLocation) {  // 返回 false 會繼續停留當前頁面,  // 否則,返回一個字符串,會顯示給用戶,讓其自己決定 if (!this.state.isSaved) return '確認要離開?'; }, }) ) 

上面代碼中,setRouteLeaveHook方法為Leave鈎子指定routerWillLeave函數。該方法如果返回false,將阻止路由的切換,否則就返回一個字符串,提示用戶決定是否要切換。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM