理解路由

理解Web路由

什么是路由

在Web開發過程中,經常會遇到『路由』的概念。那么,到底什么是路由?簡單來說,路由就是URL到函數的映射。

router和route的區別

route就是一條路由,它將一個URL路徑和一個函數進行映射,例如:

/users        ->  getAllUsers()
/users/count  ->  getUsersCount()

這就是兩條路由,當訪問/users的時候,會執行getAllUsers()函數;當訪問/users/count的時候,會執行getUsersCount()函數。

而router可以理解為一個容器,或者說一種機制,它管理了一組route。簡單來說,route只是進行了URL和函數的映射,而在當接收到一個URL之后,去路由映射表中查找相應的函數,這個過程是由router來處理的。一句話概括就是 “The router routes you to a route“。

服務器端路由

對于服務器來說,當接收到客戶端發來的HTTP請求,會根據請求的URL,來找到相應的映射函數,然后執行該函數,并將函數的返回值發送給客戶端。對于最簡單的靜態資源服務器,可以認為,所有URL的映射函數就是一個文件讀取操作。對于動態資源,映射函數可能是一個數據庫讀取操作,也可能是進行一些數據的處理,等等。

Express為例,

app.get('/', (req, res) => {
  res.sendFile('index')
})

app.get('/users', (req, res) => {
  db.queryAllUsers()
    .then(data => res.send(data))
})

這里定義了兩條路由:

  • 當訪問/的時候,會返回index頁面
  • 當訪問/users的時候,會從數據庫中取出所有用戶數據并返回

不僅僅是URL在router匹配route的過程中,不僅會根據URL來匹配,還會根據請求的方法來看是否匹配。例如上面的例子,如果通過POST方法來訪問/users,就會找不到正確的路由。

客戶端路由

對于客戶端(通常為瀏覽器)來說,路由的映射函數通常是進行一些DOM的顯示和隱藏操作。這樣,當訪問不同的路徑的時候,會顯示不同的頁面組件。客戶端路由最常見的有以下兩種實現方案:

  • 基于Hash
  • 基于History API
    (1) 基于Hash
    我們知道,URL中#及其后面的部分為hash。例如:
const url = require('url')
var a = url.parse('http://example.com/a/b/#/foo/bar')
console.log(a.hash)
// => #/foo/bar

hash僅僅是客戶端的一個狀態,也就是說,當向服務器發請求的時候,hash部分并不會發過去。

通過監聽window對象的hashChange事件,可以實現簡單的路由。例如:

window.onhashchange = function() {
  var hash = window.location.hash
  var path = hash.substring(1)

  switch (path) {
    case '/':
      showHome()
      break
    case '/users':
      showUsersList()
      break
    default:
      show404NotFound()
  }
}

(2) 基于History API
通過HTML5 History API可以在不刷新頁面的情況下,直接改變當前URL。詳細用法可以參考:

window.onpopstate = function() {
  var path = window.location.pathname

  switch (path) {
    case '/':
      showHome()
      break
    case '/users':
      showUsersList()
      break
    default:
      show404NotFound()
  }
}

但是這種方法只能捕獲前進或后退事件,無法捕獲pushState和replaceState,一種最簡單的解決方法是替換pushState方法,例如:

var pushState = history.pushState
history.pushState = function() {
  pushState.apply(history, arguments)

  // emit a event or just run a callback
  emitEventOrRunCallback()
}

不過,最好的方法還是使用實現好的history庫。
(3) 兩種實現的比較
總的來說,基于Hash的路由,兼容性更好;基于History API的路由,更加直觀和正式。

但是,有一點很大的區別是,基于Hash的路由不需要對服務器做改動,基于History API的路由需要對服務器做一些改造。下面來詳細分析。

假設服務器只有如下文件(script.js被index.html所引用):

/-
 |- index.html
 |- script.js

基于Hash的路徑有:

http://example.com/
http://example.com/#/foobar

基于History API的路徑有:

http://example.com/
http://example.com/foobar

當直接訪問 http://example.com/ 的時候,兩者的行為是一致的,都是返回了index.html文件。

當從http://example.com/ 跳轉到http://example.com/#/foobar 或者http://example.com/foobar 的時候,也都是正常的,因為此時已經加載了頁面以及腳本文件,所以路由跳轉正常。

當直接訪問http://example.com/#/foobar 的時候,實際上向服務器發起的請求是http://example.com/ ,因此會首先加載頁面及腳本文件,接下來腳本執行路由跳轉,一切正常。

當直接訪問http://example.com/foobar 的時候,實際上向服務器發起的請求也是http://example.com/foobar, 然而服務器端只能匹配/而無法匹配/foobar,因此會出現404錯誤。

因此如果使用了基于History API的路由,需要改造服務器端,使得訪問/foobar的時候也能返回index.html文件,這樣當瀏覽器加載了頁面及腳本之后,就能進行路由跳轉了。

動態路由

上面提到的例子都是靜態路由,也就是說,路徑都是固定的。但是有時候我們需要在路徑中傳入參數,例如獲取某個用戶的信息,我們不可能為每個用戶創建一條路由,而是在通過捕獲路徑中的參數(例如用戶id)來實現。

例如在Express中:

app.get('/user/:id', (req, res, next) => {
  // ... ...
})

Flask中:

@app.route('/user/<user_id>')
def get_user_info(user_id):
    pass

嚴格路由

在很多情況下,會遇到/foobar和/foobar/的情況,它們看起來非常似,然而實際上有所區別,具體的行為也是視服務器設置而定。

Flask的文檔中,提到,末尾有斜線的路徑,類比于文件系統的一個目錄;末尾沒有斜線的路徑,類比于一個文件。因此訪問/foobar的時候,可能會重定向到/foobar/,而反過來則不會。

如果使用的是Express,默認這兩者是一樣的,也可以通過app.set來設置strict routing,來區別對待這兩種情況。

原文鏈接:http://syaning.com/2017/01/10/web-route/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容