React Router 使用教程

cn915011 9年前發布 | 60K 次閱讀 React JavaScript開發工具包

真正學會 React 是一個漫長的過程。

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

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

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

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

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

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

一、基本用法

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 組件;訪問 /about ( http://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>

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

<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 || &lt;Home/>} 這樣的寫法。這時, Home 明明是 Accounts 和 Statements 的同級組件,卻沒有寫在 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 組件。

這是因為對于根路由來說, activeStyle 和 activeClassName 會失效,或者說總是生效,因為 / 會匹配任何子路由。而 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)
  },
})

十二、路由的鉤子

每個路由都有 Enter 和 Leave 鉤子,用戶進入或離開該路由時觸發。

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

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

  • /messages/:id 的 onLeave
  • /inbox 的 onLeave
  • /about 的 onEnter

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

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

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

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 ,將阻止路由的切換,否則就返回一個字符串,提示用戶決定是否要切換。

(完)

 

來自: http://www.ruanyifeng.com/blog/2016/05/react_router.html

 

 本文由用戶 cn915011 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!