理解Web路由(淺談先後端路由與先後端渲染)

1.什麼是路由?

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

路由的概念最開始是由後端提出來的,在之前用模板引擎開發頁面的時候,是使用路由返回不一樣的頁面,php

大體流程能夠當作這樣:css

(1)瀏覽器發出請求html

(2)服務器端監聽到80端口或者443有請求過來,並解析url路徑前端

(3)根據服務器的路由配置,返回相應信息(能夠是html文件,json數據,也能夠是圖片)vue

(4)瀏覽器根據數據包的content-type來決定如何解析數據html5

簡單來講路由就是用來跟後端服務器進行交互的一種方式,經過不一樣的路徑來請求不一樣的資源,請求不一樣的頁面是路由的其中一項功能。java

2.router 和 route 的區別

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

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

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

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

3.服務端路由

對於服務器來講,當接收到客戶端發來的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,就會找不到正確的路由。

4.客戶端路由

對於客戶端(一般爲瀏覽器)來講,路由的映射函數一般是進行一些DOM的顯示和隱藏操做。這樣,當訪問不一樣的路徑的時候,會顯示不一樣的頁面組件。客戶端路由最多見的有如下兩種實現方案:

(1)基於Hash

咱們知道,URL中 # 及其後面的部分爲 hash。例如:

const url = require('url')
var a = url.parse('http://example.com/#/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) //截取指定下標直接的字符,這是從下標1開始到結尾

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

(2)基於History API

經過HTML5 History API能夠在不刷新頁面的狀況下,直接改變當前URL。詳細用法能夠參考:

咱們能夠經過監聽 window 對象的 popstate 事件,來實現簡單的路由:

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 

當直接訪問 / 的時候,二者的行爲是一致的,都是返回了 index.html 文件。

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

 

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

當直接訪問 /foobar 的時候,實際上向服務器發起的請求也是 /foobar,然而服務器端只能匹配 / 而沒法匹配 /foobar,所以會出現404錯誤。

所以若是使用了基於History API的路由,須要改造服務器端,使得訪問 /foobar 的時候也能返回 index.html 文件,這樣當瀏覽器加載了頁面及腳本以後,就能進行路由跳轉了。

5.動態路由

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

例如在 Express 中:

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

在 Flask 中:

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

6.嚴格路由

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

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

若是使用的是 Express,默認這二者是同樣的,也能夠經過 app.set 來設置 strict routing,來區別對待這兩種狀況。

什麼是路由?

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

訪問的URL會映射到相應的函數裏(這個函數是廣義的,能夠是前端的函數也能夠是後端的函數),而後由相應的函數來決定返回給這個URL什麼東西。路由就是在作一個匹配的工做

從後端路由講起

在web開發早期的「刀耕火種」年代裏,一直是後端路由佔據主導地位。無論是php,仍是jsp、asp,用戶能經過URL訪問到的頁面,大可能是經過後端路由匹配以後再返回給瀏覽器的。經典面試題,「你從瀏覽器地址欄裏輸入www.baidu.com到你看到網頁這個過程當中經歷了什麼」其實講的也是這個道理。

在web後端,無論是什麼語言的後端框架,都會有一個專門開闢出來的路由模塊或者路由區域用來匹配用戶給出的URL地址,以及一些表單提交、ajax請求的地址。一般遇到沒法匹配的路由,後端將會返回一個404狀態碼。這也是咱們常說的404 NOT FOUND的由來。

URL 與 Methods

若是你關注RESTful API,那麼將會很熟悉下面四種發起請求的類型:GET,POST,PUT,DELETE。

它們分別對應四種基本操做:GET用來獲取資源,POST用來新建資源(也能夠用於更新資源),PUT用來更新資源,DELETE用來刪除資源。——來自阮一峯《理解RESTful架構》

雖然上面說的是RESTful API,可是實際上咱們在地址欄輸入一個URL,並回車的時候,是以GET請求發出去的。這也體現了,URL地址和請求的method也應該是一一對應。下面給出一個例子:

router.post('/user/:id', addUser) 

假如個人後端路由配置裏只有這一句路由。那麼我經過瀏覽器裏訪問:http://xxx.com/user/123的話是沒法訪問到的,也會返回一個404。由於後端只配了一個post方法的路由。若是要接受這個請求,那麼必須有以下的路由:

router.get('/user/:id', getUser) // 配置get路由 router.post('/user/:id', addUser) 

後端路由與服務端渲染

前面說了,「刀耕火種」的年代裏,網頁一般是經過後端路由直接輸出給客戶端瀏覽器的。也就是網頁的html通常是在後端服務器裏經過模板引擎渲染好再交給前端的。至於一些其餘的效果,是經過預先寫在頁面裏的jQuery、Bootstrap等常見的前端框架去負責的。

若是你說有些網站已是經過ajax去實現的頁面,好比gmail,好比qq郵箱。那麼你要注意到哪怕是這些頁面,它們頁面的「龍骨」也並不是是所有經過ajax去實現的,依然仍是後端直出——這也就是咱們如今又老生常談的服務端渲染。

服務端渲染的好處有不少,好比對於SEO友好,一些對安全性要求高的頁面採用服務端渲染是更保險的。而在當時尚未node.js的年代,爲了良好地構建前端頁面,都是經過服務端語言對應的模板引擎來實現動態網頁、頁面結構的組織、組件的複用。好比Laravel的blade,用在Django上的jinja2,用在Struts的jsp等等。實際上到現在,一門後端語言想要能實現本身的web功能,都須要有本身對應的模板引擎。

node.js誕生以後,前端擁有本身的後端渲染的模板引擎也成爲了現實。常見的好比pug、ejs、nunjucks等。這些模板引擎搭配Express、Koa等後端框架也在一開始風靡一時

不過在這個過程當中,隨着web應用的開發愈來愈複雜,單純服務端渲染的問題開始慢慢的暴露出來了——耦合性太強了,jQuery時代的頁面很差維護,頁面切換白屏嚴重等等。耦合性問題雖然能經過良好的代碼結構、規範來解決,不過jQuery時代的頁面很差維護這是有目共睹的,全局變量滿天飛,代碼入侵性過高。後續的維護一般是在給前面的代碼打補丁。而頁面切換的白屏問題雖然能夠經過ajax、或者iframe等來解決,可是在實現上就麻煩了——進一步增長了可維護的難度

因而,咱們開始進入了前端路由的時代

過渡到前端路由

前端路由——顧名思義,頁面跳轉的URL規則匹配由前端來控制。而前端路由主要是有兩種顯示方式:

  • 帶有hash的前端路由,優勢是兼容性高。缺點是URL帶有#號很差看
  • 不帶hash的前端路由,優勢是URL不帶#號,好看。缺點是既須要瀏覽器支持也須要後端服務器支持

前端路由應用最普遍的例子就是當今的SPA的web項目。無論是Vue、React仍是Angular的頁面工程,都離不開相應配套的router工具。前端路由帶來的最明顯的好處就是,地址欄URL的跳轉不會白屏了——這也得益於前端渲染帶來的好處。

前端路由與前端渲染

講前端路由就不能不說前端渲染。我以Vue項目爲例。若是你是用官方的vue-cli搭配webpack模板構建的項目,你有沒有想過你的瀏覽器拿到的html是什麼樣的?是你頁面長的那樣有button有form的樣子麼?我想不是的。在生產模式下,你看看構建出來的index.html長什麼樣:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="xxxx.xxx.js"></script> <script type="text/javascript" src="yyyy.yyy.js"></script> <script type="text/javascript" src="zzzz.zzz.js"></script> </body> </html> 

一般長上面這個樣子。能夠看到,這個其實就是你的瀏覽器從服務端拿到的html。這裏面空蕩蕩的只有一個 <div id="app"></div>這個入口的div以及下面配套的一系列js文件。因此你看到的頁面實際上是經過那些js渲染出來的。這也是咱們常說的前端渲染

前端渲染把渲染的任務交給了瀏覽器,經過客戶端的算力來解決頁面的構建,這個很大程度上緩解了服務端的壓力。並且配合前端路由,無縫的頁面切換體驗天然是對用戶友好的。不過帶來的壞處就是對SEO不友好畢竟搜索引擎的爬蟲只能爬到上面那樣的html,對瀏覽器的版本也會有相應的要求。

須要明確的是,只要在瀏覽器地址欄輸入URL再回車,是必定會去後端服務器請求一次的。而若是是在頁面裏經過點擊按鈕等操做,利用router庫的api來進行的URL更新是不會去後端服務器請求的。

Hash模式

hash模式利用的是瀏覽器不會對#號後面的路徑對服務端發起路由請求。也即在瀏覽器裏輸入以下這兩個地址:http://localhost/#/user/1和http://localhost/其實到服務端都是去請求http://localhost這個頁面的內容。

前端的router庫經過捕捉#號後面的參數、地址,來告訴前端庫(好比Vue)渲染對應的頁面。這樣,無論是咱們在瀏覽器的地址欄輸入,或者是頁面裏經過router的api進行的跳轉,都是同樣的跳轉邏輯。因此這個模式是不須要後端配置其餘邏輯的,後臺只要給前端返回http://localhost對應的html,剩下具體是哪一個頁面,就由前端路由去判斷即可。

History模式

不帶#號的路由,也就是咱們一般能見到的URL形式。router庫要實現這個功能通常都是經過HTML5提供的history這個api。好比history.pushState()能夠向瀏覽器地址欄push一個URL,而這個URL是不會向後端發起請求的!經過這個特性,便能很方便地實現漂亮的URL。不過須要注意的是,這個api對於IE9及其如下版本瀏覽器是不支持的,IE10開始支持,因此對於瀏覽器版本是有要求的。vue-router會檢測瀏覽器版本,當沒法啓用history模式的時候會自動降級爲hash模式

上面說了,你在頁面裏的跳轉,一般是經過router的api去進行的跳轉,router的api調用的一般是history.pushState()這個api,因此跟後端沒什麼關係。可是一旦你從瀏覽器地址欄裏輸入一個地址,好比http://localhost/user/1,這個URL是會向後端發起一個get請求的。後端路由表裏若是沒有配置相應的路由,那麼天然就會返回一個404了!這也就是不少朋友在生產模式遇到404頁面的緣由

那麼不少人會問了,那爲何我在開發模式下沒問題呢?那是由於vue-cli在開發模式下幫你啓動的那個express開發服務器幫你作了這方面的配置。理論上在開發模式下原本也是須要配置服務端的,只不過vue-cli都幫你配置好了,因此你就不用手動配置了。

那麼該如何配置呢?其實在生產模式下配置也很簡單,參考vue-router給出的配置例子。一個原則就是,在全部後端路由規則的最後,配置一個規則,若是前面其餘路由規則都不匹配的狀況下,就執行這個規則——把構建好的那個index.html返回給前端。這樣就解決了後端路由拋出的404的問題了,由於只要你輸入了http://localhost/user/1這地址,那麼因爲後端其餘路由都不匹配,那麼就會返回給瀏覽器index.html。

瀏覽器拿到這個html以後,router庫就開始工做,開始獲取地址欄的URL信息,而後再告訴前端庫(好比Vue)渲染對應的頁面。到這一步就跟hash模式是相似的了

固然,因爲後端沒法拋出404的頁面錯誤,404的URL規則天然是交給前端路由來決定了。你能夠本身在前端路由裏決定什麼URL都不匹配的404頁面應該顯示什麼。設置默認路由

前端路由與服務端渲染

雖然前端渲染有諸多好處,不過SEO的問題,仍是比較突出的。因此react、vue等框架在後來也在服務端渲染上作着本身的努力。基於前端庫的服務端渲染跟之前基於後端語言的服務端渲染又有所不一樣。前端框架的服務端渲染大多依然採用的是前端路由,而且因爲引入了狀態統1、vnode等等概念,它們的服務端渲染對服務器的性能要求比php等語言基於的字符串填充的模板引擎渲染對於服務器的性能要求高得多。因此在這方面不只是框架自己在不斷改進算法、優化,服務端的性能也必需要有所提高。當初掘金換成SSR的時候也遇到了對應的性能問題,就是這個緣由。

固然在兩者之間,也出現了預渲染的概念。也即先在服務端構建出一部分靜態的html文件,用於直出瀏覽器。而後剩下的頁面再經過經常使用的前端渲染來實現。一般咱們能夠把首頁採用預渲染的方式。這個的好處是明顯的,兼顧了SEO和服務器的性能要求。不過它沒法作到全站SEO,生產構建階段耗時也會有所提升,這也是遺憾所在。

關於預渲染,能夠考慮使用prerender-spa-plugin這個webapck的插件,它的3.x版本開始使用puppeteer來構建html文件了。

先後端分離

得益於前端路由和現代前端框架的完整的先後端渲染能力,跟頁面渲染、組織、組件相關的東西,後端終於能夠不用再參與了。

先後端分離的開發模式也逐漸開始普及。前端開始更加註重頁面開發的工程化、自動化,然後端則更專一於api的提供和數據庫的保障。代碼層面上耦合度也進一步下降,分工也更加明確。咱們也擺脫了當初「刀耕火種」的web開發年代。

實列:使用Vue 和 Flask中建立單頁面應用去除導航欄中的#號

Vue項目去掉地址欄中的#號,最經常使用的方式就是在路由中使用 history 模式,存在的問題與緣由如上文所述,HTML5的History-Mode在Vue-router中須要配置Web服務器的重定向,將全部路徑指向index.html

以Flask建立的 web server 爲例,作法很簡單,將現有路由修改成如下:

@app.route('/', defaults={'path': ''}) @app.route('/<path:path>') def catch_all(path): return render_template("index.html") 

如今輸入網址localhost:5000/xxxx 都將從新定向到index.html和vue-router將處理路由。

轉載 https://my.oschina.net/u/1778998/blog/3069956

參考: 使用 Vue.js 和 Flask 來構建一個單頁的App

相關文章
相關標籤/搜索