使用 React-Router 建立單頁應用

最近業餘時間在學習 React,配合 Redux 和 React-Router 正在不緊不慢地開發一個小工具moviemaster,用於管理硬盤中的電影劇集。在單頁應用開發中,redux 並非必須的,因此今天只講講 前端的路由系統以及 React-Router的簡單使用。html

什麼是路由

如下來自維基百科::前端

路由(routing)就是經過互聯的網絡把信息從源地址傳輸到目的地址的活動。路由發生在OSI網絡參考模型中的第三層即網路層。
路由引導分組轉送,通過一些中間的節點後,到它們最後的目的地。react

這是網絡工程中的術語,對你們而言,最熟悉的應該就是家裏的路由器。路由是指路由器從一個接口上收到數據包,根據數據包的目的地址進行定向並轉發到另外一個接口的過程。放在 Web 上來講,url 就像是路由器中的路由表,每一個 url 對應不一樣的頁面或者內容,就像路由表中的的 IP 對應不一樣的網絡同樣。git

先來看一下熟悉的套路:github

image_1b0a1gh7ge4u1g9l14mm7v41me9a.png

在傳統的網頁應用架構中,客戶端只是一個展現層,經過 url 訪問服務端,服務端則根據本身的「路由表」將對應的頁面分發給客戶端。可是在這種模式下,ajax 異步加載的內容是沒法經過url 記錄的。不管你在頁面上操做了多少,異步請求了多少數據,在每次從新訪問同一個 url 時,服務端返回給客戶端的內容都是如出一轍。ajax

image_1b0a24tg94le1p03qa76br1apfg.png

若是前端有本身專屬的「路由表」來分發頁面上不一樣的狀態,那不就好了?redux

Hash 和 pushState

據我所知,目前有兩種方式能夠構建出前端的路由系統:url 中的#和 HTML5中的 history API。其原理以下:瀏覽器

  1. 阻止標籤的默認跳轉動做。bash

  2. ajax或者 Fetch 請求內容。網絡

  3. 將返回的內容添加到頁面中。

  4. 使用 hash 或者 pushState 修改 url。

經典的 Hash

#表明網頁中的一個位置。後面接着的字符,就是該位置的標識符。好比,

https://zhanglun.github.io/index.html#body

就表明網頁 index.html 的 body 位置。瀏覽器讀取這個 URL 後,會自動將body位置滾動至可視區域。標識符的指定有兩個方法。

  1. 使用錨點

<a name="body"></a>
  1. 使用id屬性

<div id="body" >

#是用來指向文檔的內容,屬於瀏覽器的行爲,與服務端無關,在 HTTP請求中也不會攜帶 #及其後面的內容,對於服務端而言 http://www.baidu.comhttp://www.baidu.com#action=fuckbaidu 返回給客戶端的都是前者所分發的內容,可是在瀏覽器中能夠經過 Window 對象上的 location.hash 進行操做。所以,在瀏覽器中能夠經過 hash 來記錄頁面的狀態,構建「路由表」。當頁面狀態發生變化時,hash 相應變化,從新加載時又能夠經過 url 中攜帶的 hash 直接將頁面設置到對應的狀態。

好比:

http://www.example.com/
http://www.examplt.com/#edit
http://www.examplt.com/#settings
  1. 訪問/時,呈現主頁。

  2. 點擊頁面上的Edit按鈕,頁面呈現編輯對應的內容。經過 url 直接訪問時,檢查 hash 是否和 edit 匹配,若是匹配執行加載編輯內容的代碼

  3. 點擊頁面上的Settings按鈕,頁面呈現設置對應的內容。經過 url 直接訪問時,檢查 hash 是否和 settings 匹配,若是匹配執行加載編輯內容的代碼。

如下是僞代碼:

function hashHandler () {
    let key = location.hash.slice(1);
    switch(key) {
      case 'edit':
        renderEditPanel();
        break;
      case 'settings':
        renderSettings();
        break;
       default:
        break;
    }
  }
  window.onload = () => {
    hashHandler();
  }
  window.onhashchange = () => {
    hashHandler();
  }

HTML5 中的 pushState

pushState是 History API中的一個方法,其文檔能夠看這裏 MDN History。它的功能簡單的說就是:修改 url,添加歷史記錄。好比/blogssettings對應的是兩個頁面,若是隻是在頁面上點擊標籤切換,須要作的操做只有:發送請求修改頁面內容和調用 pushState 方法修改 url。問題來了,對於前端而言須要將其視爲同一個頁面,但實際上這兩個 url 對於服務端來講是兩個不一樣的請求,因此這裏須要服務端的配合。

個人作法是:對應的url 返回的都是同一個頁面,而後瀏覽器接受以後檢查前端定義路由系統,執行響應的代碼。這個方法可能會形成頁面平白添加一個短暫的延遲,不過影響不是很大。

React-Router的使用

目前來講,任何一個路由系統庫或者框架,雖然說是寫法不一,可是都是在上述兩種方式的基礎上實現的。讓我以爲耳目一新的是:使用路由嵌套的概念來定義 view 的嵌套集合,當一個給定的 URL 被調用時,整個集合中(命中的部分)都會被渲染。

import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';

import App from './containers/App';
import MovieContainer from './containers/Movies';
import Detail from './containers/Detail';


let rootElement = document.getElementById('app');
render(
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>,
rootElement);

在入口文件中,引入 React-Router,以組件的形式在 render 中使用,上述代碼配置結果以下:

URL 組件
/ App
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

在路由中,組件對應設置的子組件能夠經過 this.props.children 渲染在父組件中

class App extend Component {
  constructor(props) {
    super(props)
  }
  render() {
    <div id="app">
      <h1>Hello, world!</h1>
      {this.props.children}
    </div>
  }
}

當 URL 爲 / 時, App 中並無渲染任何的組件,render 中的 this.props.children 仍是 undefined。此時可使用 IndexRoute 來設置一個默認頁面。

render(
  <Router>
    <Route path="/" component={App}>
      {/* 當 url 爲/時渲染 Welcome */}
      <IndexRoute component={Welcome} />
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
      </Route>
    </Route>
  </Router>,
rootElement);
URL 組件
/ App -> Welcome
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

看一下這一段代碼

<Route path="posts" component={Post}>
  <Route path="users/:userid" component={User}>
    <Route path="messages/:messageid" component={Message} />
  </Route>
</Route>

此時匹配的路由分別是:/posts/posts/usres/:userid/posts/users/:userid/messages/:messageid,能夠看出,嵌套的<Route>所匹配的 url是包裹着它的 <Route>的 path 「之和」。可是問題又來了,嵌套的好處在於路由之間結構清晰直觀,可是也會致使 url 的不美觀,試想/posts/users/:userid/messages/:messageid這麼長的路由也是着實讓人心累。React-Router 的配置提供了一個選擇:將 Route 的 path 設置成絕對路徑。同時可使用<Redirect/> 將修改成絕對路徑的路由重定向到以前的設置

<Route path="posts" component={Inbox}>
  <Route path="/users/:userid" component={Message}>
    <Route path="/messages/:messageid" component={Message} />
  </Route>
</Route>
URL 組件
/posts App -> Post
/user/:userid App -> Post -> User
/messages/:messageid App -> Post -> User ->Message

基礎的配置完成以後,經過 <Link>自動或者經過browserHistoryhashHistory手動執行路由的跳轉。

相關文章
相關標籤/搜索