談談 react-router

談談

最近使用的 React + webpack 來開發項目,感受確實是爽的飛起,然而總感受仍是少了點什麼。
對,是多頁面,每次請求頁面還要後端路由給你?多不爽啊,試試 react-router ,簡直掌控一切的感受,只開放一個頁面路由接口,其餘全給數據接口就了,能夠和後端哥哥說拜拜了。
??????????? 啪啪啪啪啪~html

react-router.png

概述

先貼上官方文檔 https://github.com/rackt/react-router/tree/master/docs.
對了這裏還有一份中文文檔(不過不是很全)http://react-guide.github.io/react-router-cn/.
react-router 是 React 的完整前端路由解決方案,特別在作一個 spa 應用的時候,他能實現 url 和 視圖ui 的同步,而且支持後端渲染,異步按需加載等等。
因爲 react-router 文檔的多變,這裏的例子以當前版本 1.0.1 爲準。(1.0以前文檔每個版本的變更都很大,索多了都是淚)前端

瀏覽器支持

全部支持 React 的瀏覽器。
倡議!建議你們也勸說身邊的人用現代化瀏覽器,關愛前端開發者。react

安裝

npm install react-router@latest

同時,react-router 是基於 history 開發的,這裏你須要安裝 history。
注意 react-router 當前版本 1.0.1 依賴的是 history 1.13.1 請不要安裝最新版。
不要問我爲何知道,被坑慘了;有同窗問,那沒有辦法用 history 的最新版本嘛?畢竟這只是暫緩之計,解決方案仍是有的,那就是等 react-router 做者解決咯 ?。webpack

npm install history@1.13.1

構建工具的話,我依然建議是 webpack , React 和 webpack 是一對好兄弟。git

npm install webpack

webpack的使用方法能夠看個人前兩篇文章:es6

小例子

// 加載依賴包,這是 es6 的語法(我好囉嗦)。
import React from 'react'
import { render } from 'react-dom'
// 這裏從 react-router 引入了三個組件,先不解釋。
import { Router, Route, Link } from 'react-router'

const App = React.createClass({
  render() {
    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/inbox">Inbox</Link></li>
        </ul>
        {this.props.children}
      </div>
    )
  }
});

const Inbox = React.createClass({
  render() {
    return (
      <div>
        Inbox
      </div>
    )
  }
});

const About = React.createClass({
  render() {
    return (
      <div>
        About
      </div>
    )
  }
});

render((
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox} />
    </Route>
  </Router>
), document.getElementById('root'));

我就偷個懶把官方文檔的demo直接copy了。
直接先來看 render 下面的內容,這裏是用 jsx 語法。 最外層組件是 Router(能夠把他看做是react-router提供的最外層容器) , 下一層是 Route,這個是路由組件。對應關係以下:
/ ---> <App />
/about ---> <App><About /></App>
/inbox ---> <App><Inbox /></App>
path 對應路徑,component 對應當前路徑渲染的組件。
Route 裏面的 Route 表示在父組件路由的 path 路徑下面的一級 path 對應的路由,這裏的路由是父子嵌套,對應的組件也一樣是父子嵌套的。
若是是多級嵌套也一樣如此。算法

不想用 jsx 來寫,換個方式吧~

以上例子能夠改寫成:npm

...
const routerConfig = [
  {
    path: '/',
    component: App,
    childrenRoutes: [
      { path: 'about', component: About },
      { path: 'inbox', component: Inbox },
    ]
  }
];
render((
  <Router routes={routeConfig} />
), document.getElementById('root'));

這裏的結構就更清晰了,我是比較喜歡這種方式。

默認的路由

好比上面的例子,/ 對於的組件是 App , 若是 App 只是渲染了一個導航條,卻沒有自組件,那打開 好比 qiutc.me/ 的時候不是就沒有內容了嗎。
把上面例子改一下

...
// 添加組件 Index
const Index = React.createClass({
  render() {
    return (
      <div>
        Index
        Index
        Index
      </div>
    )
  }
});
// 修改配置
const routerConfig = [
  {
    path: '/',
    component: App,
    indexRoute: { component: Index },
    childrenRoutes: [
      { path: 'about', component: About },
      { path: 'inbox', component: Inbox },
    ]
  }
];

這裏加了一個 indexRoute ,表示他在沒有匹配子路由的時候,在 / 路由下渲染默認的子組件 Index。
路由組件嵌套也是同樣的,好比:

{ path: 'about', component: About, indexRoute: {component: AboutIndex} },

以此類推。

404 NotFound

若是咱們打開了一個沒有設置路由的連接,就必然須要一個友好的 404 頁面。配置以下:

...
// 添加 404 組件
const NotFound = React.createClass({
  render() {
    return (
      <div>
        404
        NotFound
      </div>
    )
  }
});
// 修改配置
const routerConfig = [
  {
    path: '/',
    component: App,
    indexRoute: { component: Index },
    childrenRoutes: [
      { path: 'about', component: About },
      { path: 'inbox', component: Inbox },
    ]
  },
  {
    path: '*',
    component: NotFound,
  }
];

如此簡單。

絕對路徑與重定向

const routerConfig = [
  {
    path: '/',
    component: App,
    indexRoute: { component: Index },
    childrenRoutes: [
      { path: 'about', component: About },
      {
        path: 'inbox',
        component: Inbox,
        childrenRoutes: [
          {
            path: 'message/:id',
            component: Message,
          }
        ],
      },
    ]
  },
  {
    path: '*',
    component: NotFound,
  }
];

在這裏咱們訪問 /inbox/message/1 對於渲染 Message 組件,這個連接太長了,咱們想直接 /message/1 那怎麼辦,改路由結構?太麻煩了!絕對路徑能夠幫你作到這個。
path: 'message/:id', 改成 path: '/message/:id', 就行了。
等等若是用戶以前收藏的連接是 /inbox/message/1 ,那不是就打不開了嘛,和後端路由同樣,react-router 也有重定向:redirect

const routerConfig = [
  {
    path: '/',
    component: App,
    indexRoute: { component: Index },
    childrenRoutes: [
      { path: 'about', component: About },
      {
        path: 'inbox',
        component: Inbox,
        childrenRoutes: [
          {
            path: '/message/:id',
            component: Message,
          },
          {
            path: 'message/:id',
            onEnter: function (nextState, replaceState) {
              replaceState(null, '/messages/' + nextState.params.id);
            }
          }
        ],
      },
    ]
  },
  {
    path: '*',
    component: NotFound,
  }
];

onEnter 方法表示進入這個路由前執行的方法,在進入 /inbox/messages/:id 的前,執行

function (nextState, replaceState) {
  replaceState(null, '/messages/' + nextState.params.id);
}

nextState表示要進入的下一個路由,這裏就是 /inbox/messages/:id ,replaceState 表示替換路由狀態的方法,把 /inbox/messages/:id 替換成 /messages/:id,而後就能夠重定向到 /messages/:id。

一樣的也有 onLeave 這個方法

表示在離開路由前執行。
———————————————————
固然若是你用的是 jsx 語法,有更簡單的組件能夠實現:

import { Redirect } from 'react-router'

React.render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Index} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="/messages/:id" component={Message} />
        {/* 跳轉 /inbox/messages/:id 到 /messages/:id */}
        <Redirect from="messages/:id" to="/messages/:id" />
      </Route>
    </Route>
  </Router>
), document.getElementById('root'))

路徑匹配原理

嵌套關係

React Router 使用路由嵌套的概念來讓你定義 view 的嵌套集合,當一個給定的 URL 被調用時,整個集合中(命中的部分)都會被渲染。嵌套路由被描述成一種樹形結構。React Router 會深度優先遍歷整個理由配置來尋找一個與給定的 URL 相匹配的路由。

簡單來說,就是說,匹配的時候會先匹配到外層路徑,而後依次遍歷到內層。
好比 /inbox/messages/:id 會先匹配 /,渲染 / 對應的組件 App,而後再到 / 的下一層尋找 /inbox ,一樣渲染 /inbox 對應的組件 Inbox,依次類推,直到到 message/:id。

tip:使用絕對路徑能夠忽略嵌套關係,如上面例子。

路徑語法

路由路徑是匹配一個(或一部分)URL 的 一個字符串模式。大部分的路由路徑均可以直接按照字面量理解,除了如下幾個特殊的符號:

  • /:paramName – 匹配一段位於 /、? 或 # 以後的 URL。 命中的部分將被做爲一個參數

  • (/) – 在它內部的內容被認爲是可選的

  • /* – 匹配任意字符(非貪婪的)直到命中下一個字符或者整個 URL 的末尾,並建立一個 splat 參數

    <Route path="/hello/:name">         // 匹配 /hello/michael 和 /hello/ryan
    <Route path="/hello(/:name)">       // 匹配 /hello, /hello/michael 和 /hello/ryan
    <Route path="/files/*.*">           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

優先級

最後,路由算法會根據定義的順序自頂向下匹配路由。所以,當你擁有兩個兄弟路由節點配置時,你必須確認前一個路由不會匹配後一個路由中的路徑。例如:

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

第二個是不會被執行的。

拿到參數路徑的

好比上面的 /messages/:id ,這個id多是咱們在 Message 獲取數據時須要的 id。
他會被當作一個參數傳給 params,parmas 會傳給 Message 組件的 props:

const Message = React.createClass({
    render: function() {
      return (
        <div>{ this.props.params.id }</div>
      );
    }
});

這樣就能夠獲取到了。

history 配置

React Router 是創建在 history 之上的。 簡而言之,一個 history 知道如何去監聽瀏覽器地址欄的變化, 並解析這個 URL 轉化爲 location 對象, 而後 router 使用它匹配到路由,最後正確地渲染對應的組件。
經常使用的 history 有三種形式, 可是你也可使用 React Router 實現自定義的 history。

  • createHashHistory

  • createBrowserHistory

  • createMemoryHistory

這三個有什麼區別呢:

createHashHistory

這是一個你會獲取到的默認 history ,若是你不指定某個 history 。它用到的是 URL 中的 hash(#)部分去建立形如 example.com/#/some/path 的路由。
這個 支持 ie8+ 的瀏覽器,可是由於是 hash 值,因此不推薦使用。

createBrowserHistory

Browser history 是由 React Router 建立瀏覽器應用推薦的 history。它使用 History API 在瀏覽器中被建立用於處理 URL,新建一個像這樣真實的 URL example.com/some/path。

Memoryhistory

不會在地址欄被操做或讀取。

使用

import { createBrowserHistory, useBasename } from 'history';
const historyConfig = useBasename(createHistory)({
  basename: '/'        // 根目錄名
});
...
render((
  <Router routes={routeConfig} History={historyConfig} />
), document.getElementById('root'));

Link&IndexLink

Link

咱們在最開頭看到這樣一個東西:

const App = React.createClass({
  render() {
    return (
      <div>
        <h1>App</h1>
        <ul>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/inbox">Inbox</Link></li>
        </ul>
        {this.props.children}
      </div>
    )
  }
});

Link 會被渲染成 a ,to 其實就是 href ,
可是 react-router 會阻止默認跳轉頁面,而改爲 history 路由的變換。

參數:

  • to
    切換到的路由地址

  • query
    跟在 url 的 query 參數,好比

query={{q: "que"}} 對應 `/example?a=que

這裏的 query 一樣能夠像 params 會被傳入下一個路由組件的 props

  • hash
    跟在 url 的 hash 參數,好比

hash={111} 對應 `/example#111

這裏的 query 一樣能夠像 params 會被傳入下一個路由組件的 props

  • activeClassName
    當前 url 路徑若是和 Link 的 to 匹配 這個 Link 就會有一個定義的屬性,好比:

在 /index 下
<Link to="/index" activeClassName={"active"} activeStyle={{color: 'red'}} >/</Link>
這裏渲染出來的 a 標籤會有一個激活的 active 類名,還會有一個顏色 red
<Link to="/about" activeClassName={"active"} activeStyle={{color: 'red'}} >/</Link>
這裏渲染出來的 a 標籤就不會有以上屬性
  • activeStyle
    同上

  • onClick
    點擊的時候執行的函數,會傳入一個 e 事件對象,你能夠 e.stopPropagation() 阻止默認路由切換。

IndexLink

在上面有一個問題若是:

在 / 下 和 /index
<Link to="/" activeClassName={"active"} activeStyle={{color: 'red'}} >/</Link>
這個 Link 渲染出來的 a 標籤都會激活 active 屬性,而且會帶上 color: 'red'
由於 / 和 /index 和 / 都是匹配的

這時候就能夠用:

<IndexLink to="/" activeClassName={"active"} activeStyle={{color: 'red'}} >/</IndexLink>
只會在 / 下唄激活,在 /index 或者其餘下面,不會被激活

未完待續

關於 根據路由按需異步加載js服務器端渲染路由視圖 以及 react-router的更高級用法 會在下一篇文章來探討。畢竟哥也須要去深刻研究一下才敢獻醜。?

相關文章
相關標籤/搜索