SPA 路由三部曲之核心原理

爲了配合單頁面 Web 應用快速發展的節奏,近幾年,各種前端組件化技術棧層出不窮。經過不斷的版本迭代 React、Vue 脫穎而出,成爲當下最受歡迎的兩大技術棧。 javascript

僅 7 個月的時間,兩個技術棧的下載量就突破了百萬,React 甚至突破了千萬。無論是現下流行的 React、Vue,仍是紅極一時的 Angular、Ember,只要是單頁面 Web 應用,都離不開前端路由的配合。若是把單頁面 Web 應用比做一間房,每一個頁面對應房子中的各個房間,那麼路由就是房間的門,無論房間裝飾的有多漂亮,沒有門,也沒法展現在用戶眼前,路由在單頁面 Web 應用的地位也就不言而喻了。 css

爲了能更詳細的介紹前端路由,小編將從三個層面,由淺入深,一步一步的帶領你們探索前端路由的實現原理。首先經過《SPA 路由三部曲之核心原理》瞭解前端路由的核心知識,緊接着《SPA 路由三部曲之 MyVueRouter 實踐》將帶領你們實現屬於本身的 vue-router,最後《SPA 路由三部曲之 VueRouter 源碼解析》將挑戰自我,深度解析 vue-router 源碼。《SPA 路由三部曲之核心原理》將從端路由的前世此生、核心原理解析、vue-router 與 react-router 應用對比三部分對前端路由進行初步瞭解。 html

前端路由前世此生

前端路由發展到今天,經歷了後端路由、先後端路由過渡、前端路由的過程,若是你對前端路由的理解仍是懵懵懂懂,那有必要了解一下它的發展過程。前端

後端路由

路由這個概念最早是在後端出現的, Web 開發還在「刀耕火種」年代時,一直是後端路由佔據主導地位,頁面渲染徹底依賴服務器。 vue

在最開始的時候,HTML、CSS、JavaScript 的文件以及數據載體 json(xml) 等文件都是放到後端服務器目錄下的,而且這些文件彼此是沒有聯繫的,想要改變網站的佈局,可能會改上百個 HTML,繁瑣且毫無技術含量。後來聰明的工程師就將相同的 HTML 整理成模板,進行復用,成功減小了前端的工做量。前端工程師開始用模板語言代替手寫 HTML,後端服務器目錄的文件也變成了不一樣的模板文件。java

這個時期,無論 Web 後端是什麼語言的框架,都會有一個專門開闢出來的路由模塊或者路由區域,用來匹配用戶給出的 URL 地址,以及一些表單提交、頁面請求地址。用戶進行頁面切換時,瀏覽器發送不一樣的 URL 請求,服務器接收到瀏覽器的請求時,經過解析不一樣的 URL 地址進行後端路由匹配,將模板拼接好後將之返回給前端完整的 HTML,瀏覽器拿到這個 HTML 文件後直接解析展現了,也就是所謂的服務端渲染。react

服務端渲染

服務端渲染頁面,後端有完整的 HTML 頁面,爬蟲更容易獲取信息,有利於 SEO 優化。對於客戶端的資源佔用更少,尤爲是移動端,能夠更省電。程序員

過渡

之後端路由爲基礎,開發的 Web 應用,都會存在一個弊端。每跳轉到不一樣的 URL,都是從新訪問服務端,服務器拼接造成完整的 HTML,返回到瀏覽器,瀏覽器進行頁面渲染。甚至瀏覽器的前進、後退鍵都會從新訪問服務器,沒有合理地利用緩存。 vue-router

隨着前端頁面複雜性愈來愈高,功能愈來愈完善,後端服務器目錄下的代碼文件會愈來愈多,耦合性也愈來愈嚴重。不只加大服務器的壓力,也不利於良好的用戶體驗,代碼維護。受限於以 JavaScript 爲表明的前端技術還沒有崛起,這個痛點成了程序員的最大難題。json

直到 1998 年,微軟的 Outloook Web App 團隊提出 Ajax 的基本概念(XMLHttpRequest 的前身),相信你們對這個技術已經很是熟悉了,瀏覽器實現異步加載的一種技術方案,並在 IE5 經過 ActiveX 來實現了這項技術。有了 Ajax 後,頁面操做就不用每次都刷新頁面,體驗帶來了極大的提高。

2005 年 Google Map 的發佈讓 Ajax 這項技術發揚光大,向人們展現了它真正的魅力,讓其不只僅侷限於簡單的數據和頁面交互,也爲後來異步交互體驗方式的繁榮發展奠基了基礎。2008 年,Google V8 引擎發佈,JavaScript 隨之崛起,前端工程師開始借鑑後端模板思想,單頁面應用就此誕生。2009 年,Google 發佈 Angularjs 將 MVVM 及單頁面應用發揚光大,由衷的佩服 Google 的強大。

單頁應用不只在頁面交互是無刷新的,連頁面跳轉都是無刷新的,爲了配合實現單頁面應用跳轉,前端路由孕育而生。

前端路由

前端路由相較於後端路由的一個特色就是頁面在不徹底刷新的狀況下進行視圖的切換。頁面 URL 變了,可是並無從新加載,讓用戶體驗更接近原生 app。

前端路由的興起,使得頁面渲染由服務器渲染變成了前端渲染。爲何這麼說呢!請求一個 URL 地址時,服務器不須要拼接模板,只需返回一個 HTML 便可,通常瀏覽器拿到的 HTML 是這樣的:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Demo</title>
  <link href="app.css" rel="stylesheet"/>
</head>
<body>
  <div id="app"></div>
  <script type="text/javascript" src="app.js"></script>
</body>
</html>

這裏空蕩蕩的只有一個 <div id="app"></div>,以及一系列的 js 文件,因此說這個 HTML 是不完整的。咱們看到的頁面是經過這一系列的 js 渲染出來的,也就是前端渲染。前端渲染經過客戶端的算力來解決頁面的構建,很大程度上緩解了服務端的壓力。

客戶端渲染

單頁面開發是趨勢,但也不能拈輕怕重,忽略前端渲染的缺點。因爲服務器沒有保留完整的 HTML,經過 js 進行動態 DOM 拼接,須要耗費額外的時間,不如服務端渲染速度快,也不利於 SEO 優化。因此說,實際開發中,不能盲目選擇渲染方式,必定要基於業務場景。對於沒有複雜交互,SEO 要求嚴格的網站,服務器渲染也是正確的選擇。

核心原理解析

路由描述了 URL 與 UI 之間的映射關係,這種映射是單向的,即 URL 變化引發 UI 更新(無需刷新頁面)。前端路由最主要的展現方式有 2 種:

  • 帶有 hash 的前端路由:地址欄 URL 中有 #,即 hash 值,很差看,但兼容性高。
  • 不帶 hash 的前端路由:地址欄 URL 中沒有 #,好看,但部分瀏覽器不支持,還須要後端服務器支持。

在 vue-router 和 react-router 中,這兩種展現形式,被定義成兩種模式,即 Hash 模式與 History 模式。前端路由實現原理很簡單,本質上就是檢測 URL 的變化,截獲 URL 地址,經過解析、匹配路由規則實現 UI 更新。如今就跟着小編一塊兒來揭開它神祕的面紗吧!

Hash

一個完整的 URL 包括:協議、域名、端口、虛擬目錄、文件名、參數、錨。

URL 組成

hash 值指的是 URL 地址中的錨部分,也就是 # 後面的部分。hash 也稱做錨點,是用來作頁面定位的,與 hash 值對應的 DOM id 顯示在可視區內。在 HTML5 的 history 新特性出現前,基本都是使用監聽 hash 值來實現前端路由的。hash 值更新有如下幾個特色:

  • hash 值是網頁的標誌位,HTTP 請求不包含錨部分,對後端無影響
  • 由於 HTTP 請求不包含錨部分,因此 hash 值改變時,不觸發網頁重載
  • 改變 hash 值會改變瀏覽器的歷史記錄
  • 改變 hash 值會觸發 window.onhashchange() 事件

而改變 hash 值的方式有 3 種:

  • a 標籤使錨點值變化,例: <a href='#/home'></a>
  • 經過設置 window.location.hash 的值
  • 瀏覽器前進鍵(history.forword())、後退鍵(history.back())

綜上所述,這 3 種改變 hash 值的方式,並不會致使瀏覽器向服務器發送請求,瀏覽器不發出請求,也就不會刷新頁面。hash 值改變,觸發全局 window 對象上的 hashchange 事件。因此 hash 模式路由就是利用 hashchange 事件監聽 URL 的變化,從而進行 DOM 操做來模擬頁面跳轉。
hash 流程圖

History

在講解 History 以前,你們先思考一個問題,點擊瀏覽器左上角的回退按鈕爲何能回到以前的瀏覽記錄,點擊前進按鈕就能回到回退以前的瀏覽記錄?這是由於瀏覽器有一個相似棧的歷史記錄,遵循先進後出的規則。URL 的每次改變,包括 hash 值的變化都會在瀏覽器中造成一條歷史記錄。window 對象經過 history 對象提供對覽器歷史記錄的訪問能力。

  • history.length
    出於安全考慮,History 對象不容許未受權代碼訪問歷史記錄中其它頁面的 URLs,但能夠經過 history.length 訪問歷史記錄對象的長度。
  • history.back()
    回退到上一個歷史記錄,同瀏覽器後退鍵
  • history.forward()
    前進到下一個歷史記錄,同瀏覽器前進鍵
  • history.go(n)
    跳轉到相應的訪問記錄;若 n > 0,則前進,若 n < 0,則後退,若 n = 0,則刷新當前頁面

爲了配合單頁面的發展,HTML5 對 History API 新增的兩個方法:pushState()、replaceState(),均具備操縱瀏覽器歷史記錄的能力。

history.pushState(state, title, URL)

pushState 共接收 3 個參數:

  • state:用於存儲該 URL 對應的狀態對象,能夠經過 history.state 獲取
  • title:標題,目前瀏覽器並不支持
  • URL:定義新的歷史 URL 記錄,須要注意,新的 URL 必須與當前 URL 同源,不能跨域

pushState 函數會向瀏覽器的歷史記錄中添加一條,history.length 的值會 +1,當前瀏覽器的 URL 變成了新的 URL。須要注意的是:僅僅將瀏覽器的 URL 變成了新的 URL,頁面不會加載、刷新。簡單看個例子:

經過 history.pushState({ tag: "cart" }, "", "cart.html"),將 /home.html 變成 /cart.html 時,只有 URL 發生了改變,cart.html 頁面並無加載,甚至瀏覽器都不會去檢測該路徑是否是存在。這也就是證實了,pushState 在不刷新頁面的狀況下修改瀏覽器 URL 連接,單頁面路由的實現也就是利用了這一個特性。

細心地童鞋應該發現了,經過 pushState 設置新的 URL 的方法與經過 window.location='#cart' 設置 hash 值改變 URL 的方法有類似之處:URL 都發生了改變,在當前文檔內都建立並激活了新的歷史記錄條目,但頁面均沒有從新渲染,瀏覽器沒有發起請求。那前者的優點又是什麼呢?

  • 新的 URL 能夠是任意同源的 URL,而 window.location,只能經過改變 hash 值才能保證留在當前 document 中,瀏覽器不發起請求
  • 新的 URL 能夠是當前 URL,不改變,就能夠建立一條新的歷史記錄項,而 window.location 必須設置不一樣的 hash 值,才能建立。假如當前URL爲 /home.html#foo,使用 window.location 設置 hash 時,hash
    值必須不能是 #foo,才能建立新的歷史記錄
  • 能夠經過 state 參數在新的歷史記錄項中添加任何數據,而經過 window.location 改變 hash 的方式,只能將相關的數據轉成一個很短的字符串,以 query 的形式放到 hash 值後面
  • 雖然 title 參數如今還不能被全部的瀏覽器支持,前端發展這麼快,誰能說的准以後發生的事情呢!

history.replaceState(state, title, URL)

replaceState 的使用與 pushState 很是類似,都是改變當前的 URL,頁面不刷新。區別在於 replaceState 是修改了當前的歷史記錄項而不是新建一個,history.length 的值保持不變。

從上面的動畫,咱們就能夠知道,經過 history.replaceState({ tag: "cart" }, "", "cart.html") 改變 URL 以前,history 的歷史記錄爲 /classify.html/home.html,URL 改變以後,點擊瀏覽器後退鍵,直接回到了 /classify.html,跳過了 /home.html。也就證實了 replaceState 將歷史記錄中的 /home.html 修改成 /cart.html,而不是新建 /cart.html

window.onpopstate()

經過 a 標籤或者 window.location 進行頁面跳轉時,都會觸發 window.onload 事件,頁面完成渲染。點擊瀏覽器的後退鍵或前進鍵,根據瀏覽器的不一樣機制,也會從新加載(Chrome 瀏覽器),或保留以前的頁面(Safari 瀏覽器)。而對於經過 history.pushState() 或 history.replaceState() 改變的歷史記錄,點擊瀏覽器的後退鍵或前進鍵頁面是沒有反應的,那該如何控制頁面渲染呢?爲了配合 history.pushState() 或 history.replaceState(),HTML5 還新增了一個事件,用於監聽 URL 歷史記錄改變:window.onpopstate()。

官方對於 window.onpopstate() 事件的描述是這樣的:

每當處於激活狀態的歷史記錄條目發生變化時,popstate 事件就會在對應 window 對象上觸發。 若是當前處於激活狀態的歷史記錄條目是由 history.pushState() 方法建立,或者由 history.replaceState() 方法修改過的, 則 popstate 事件對象的 state 屬性包含了這個歷史記錄條目的 state 對象的一個拷貝。調用 history.pushState() 或者 history.replaceState() 不會觸發 popstate 事件。popstate 事件只會在瀏覽器某些行爲下觸發, 好比點擊後退、前進按鈕(或者在JavaScript 中調用 history.back()、history.forward()、history.go()方法),此外,a 標籤的錨點也會觸發該事件。

第一次讀到這段話的時候似懂非懂,思考了好久,也作了不少的例子,發現其中的坑不少,這些坑主要是由於每一個瀏覽器機制不一樣。官方文檔對 window.onpopstate() 的描述不多,也有不少不明確的地方,根據本身的測試,來拆解一下官網描述,若是有不對的,還但願你們指出。

1.每當處於激活狀態的歷史記錄條目發生變化時,popstate 事件就會在對應 window 對象上觸發。

對這句話的理解是,在瀏覽器中輸入一個 URL ,使其處於激活狀態,無論經過哪一種方式,只要 URL 改變,popstate 就會觸發。但實際狀況倒是:只有經過 pushState 或 replaceState 改變的 URL,在點擊瀏覽器後退鍵的時候纔會觸發,若是是經過 a 標籤或 window.location 實現 URL 改變(不是改變錨點)頁面跳轉,在點擊瀏覽器回退鍵的時候,並不會觸發。對這種狀況,我有兩個猜想:

  • popstate 事件是異步函數。因爲經過 a 標籤或 window.location 實現 URL 改變時,當前頁面卸載,新的頁面加載。因爲 popstate 事件是異步的,在頁面卸載以前並沒來得及加載。
  • 只有觸發新增的 pushState 與 replaceState 改變的歷史記錄條目,纔會觸發 popstate 事件,畢竟 popstate 事件的出現是爲了配合 pushState 與 replaceState。

查閱了不少資料,這兩個猜想沒有獲得證明,但有一點能夠確定,想要監聽到 popstate 事件,必須是使用 pushState 與 replaceState 改變的歷史記錄。

2.調用 history.pushState() 或者 history.replaceState() 不會觸發 popstate 事件,popstate 事件只會瀏覽器的某些行爲下觸發。

因爲各個瀏覽器的機制不一樣,測試結果也是不一樣的。咱們先在 Chrome 瀏覽器下作個測試:
home.html

<div>
  <h3>home html</h3>
  <div id="btn" class="btn">跳轉至 cart.html</div>
  <a href="classify.html"> a 標籤跳轉至 classify.html</a>
</div>
<script>
  document.getElementById('btn').addEventListener('click', function(){
       history.replaceState({ tag: "cart" }, "", "cart.html")
   }, false); 
   window.addEventListener('popstate', ()=>{
      console.log('popstate home 跳轉')
   })
</script>

咱們進行這樣的操做:當前 URL 爲 /home.html,經過 history.pushState({ tag: "cart" }, "", "cart.html") 將當前 URL 變成了 /cart.html。這個過程當中,home.html 中的 popstate 事件確實沒有觸發。此時點擊瀏覽器後退鍵,URL 變回了/home.htmlhome.html 中的 popstate 事件觸發了。

那若是咱們跳出 /home.html 的 document 呢?經過 history.pushState({ tag: "cart" }, "", "cart.html") 將當前 URL 變成了 /cart.html 後,點擊 a 標籤將 URL 變爲 /classify.html

執行到這裏,咱們須要明確一點:a 標籤改變 URL,瀏覽器會從新發起請求,頁面發生了跳轉,window 對象也發生了改變。popstate 官方文檔第一句指出: popstate 事件是在對應 window 對象上觸發。此時,咱們點擊瀏覽器後退鍵,URL 變成 /cart.html,執行 /cart.html 中的 load 事件,頁面加載。再次點擊瀏覽器後退鍵,URL 變爲 /home.html/cart.html 中的 popstate 事件觸發,頁面未渲染。

popstate 事件雖然觸發了,可是是 cart.html 頁面中定義的 popstate 事件,並非 home.html 的事件。而且一樣的瀏覽器回退鍵操做,在 Safari 瀏覽器的展現是這樣的:

在瀏覽器回退時,Safari 瀏覽器與 Chrome 瀏覽器對於頁面的加載出現了差別。classify.html 回退到 cart.html ,URL 變成了 /cart.html,但觸發了 home.html 中的 popstate 事件,繼續回退,URL 變成了 /home.html, 依然觸發了 home.html 中 popstate 事件。

Chrome 瀏覽器與 Safari 瀏覽器差別的產生與瀏覽器對 popstate 事件處理有關係。至於瀏覽器內部是怎樣處理的,小編也沒有研究清楚。雖然 Chrome 瀏覽器與 Safari 瀏覽器對於 popstate 事件的處理方式不同,可是 URL 的回退路徑是一致的,徹底符合歷史記錄後進先出的規則。

在實際開發中,這種狀況也是存在的:URL 由 /home.html/cart.html 的改變,就相似單頁面開發中的跳轉。若此時在 cart.html 中,須要使用 pushState 跳出單頁面,進入登陸頁,用戶在登陸頁點擊瀏覽器回退,或移動端手勢返回。上述狀況就會出現,Chrome 瀏覽器與 Safari 瀏覽器渲染頁面不一致。

popstate 官網描述是「popstate 事件會在對應 window 對象上觸發」,注意是對應 window 對象,這個概念就比較模糊了,指的是觸發 pushState 的 window 對象,仍是 pushState 新定義的 window 對象。根據咱們上述的測試,都有可能觸發 popstate 事件。因此童鞋們,在遇到上面狀況時,必定不要忘記在相關的兩個頁面中都要作 popstate 監聽處理。

3.a 標籤的錨點也能夠觸發 popstate 事件的方法

與 pushState 和 replaceState 不一樣,a 標籤錨點的變化會當即觸發 popstate 事件。這裏咱們擴展一下思路,a 標籤作的事情就是改變了 hash 值,那經過 window.location 改變 hash 值是否是也是能當即觸發 popstate。答案是確定的,也會當即觸發 popstate。

經過 hash 小節的瞭解,hash 值的改變會觸發 hashchange 事件,因此,hash 值的改變會同時觸發 popstate 事件與 hashchange 事件,但若是改變的 hash 值與當前 hash 值同樣的話,hashchange 事件不觸發,popstate 事件觸發。以前咱們說過,window.location 設置的 hash 值必須與當前 hash 值不同才能新建一條歷史記錄,而 pushState 卻能夠。

結合上述,在瀏覽器支持 pushState 的狀況下,hash 模式路由也可使用 pushState 、replaceState 和 popstate 實現。pushstate 改變 hash 值,進行跳轉,popstate 監聽 hash 值的變化。小小的劇透,vue-router 中無論是 hash 模式,仍是 history 模式,只要瀏覽器支持 history 的新特性,使用的都是 history 的新特性進行跳轉。

前端路由應用

其實 history 和 hash 都是瀏覽器自有的特性,單頁面路由只是利用了這些特性。在不跳出當前 document 的狀況下,除了 history 自身的兼容性以外,各個瀏覽器都不會存在差別,而單頁面開發就是在一個 document 中完成全部的交互,這二者的完美結合,將前端開發提高到了一個新的高度。

vue-router 和 react-router 是如今最流行的路由狀態管理工具。二者實現原理雖然是一致的,但因爲所依賴的技術棧不一樣,使用方式也略有不一樣。在 react 技術棧開發時,大部分的童鞋仍是喜歡使用 react-router-dom ,它基於 react-router,加入了在瀏覽器運行環境下的一些功能。

注入方式

1. vue-router

vue-router 能夠在 vue 項目中全局使用,vue.use() 功不可沒。經過 vue.use(),向 VueRouter 對象注入了 Vue 實例,也就是根組件。根組件將 VueRouter 實例一層一層的向下傳遞,讓每一個渲染的子組件擁有路由功能。

import VueRouter from 'vue-router'
const routes = [
    { path: '/',name: 'home',component: Home,meta:{title:'首頁'} }
]
const router = new myRouter({
    mode:'history',
    routes
})
Vue.use(VueRouter)

2. react-router-dom

react-router 的注入方式是在組件樹頂層放一個 Router 組件,而後在組件樹中散落着不少 Route 組件,頂層的 Router 組件負責分析監聽 URL 的變化,在其下面的 Route 組件渲染對應的組件。在完整的單頁面項目中,使用 Router 組件將根組件包裹,就能完成保證正常的路由跳轉。

import { BrowserRouter as Router, Route } from 'react-router-dom';
class App extends Component {
    render() {
        return (
            <Router>
                <Route path='/' exact component={ Home }></Route>
            </Router>
        )
    }
}

基礎組件

1. vue-router 提供的組件主要有 <outer-link/> 和 <router-view/>

  • <router-link/> 能夠操做 DOM 直接進行跳轉,定義點擊後導航到哪一個路徑下;對應的組件內容渲染到 <router-view/> 中。

2. react-router-dom 經常使用到的是 <BrowserRouter/>、<HashRouter/>、<Route/>、<Link/>、<Switch/>

  • <BrowserRouter/>、<HashRouter/> 組件看名字就知道,用於區分路由模式,而且保證 React 項目具備頁面跳轉能力。
  • <Link /> 組件與 vue-router 中的 <router-link/> 組件相似,定義點擊後的目標導航路徑,對應的組件內容經過 <Route /> 進行渲染。
  • <Switch/> 用來將 react-router 由包容性路由轉換爲排他性路由,每次只要匹配成功就不會繼續向下匹配。vue-router 屬於排他性路由。

路由模式

1. vue-router 主要分爲 hash 和 history 兩種模式。在 new VueRouter() 時,經過配置路由選項 mode 實現。

  • Hash 模式:地址欄 URL 中有 #。vue-router 優先判斷瀏覽器是否支持 pushState,若支持,則經過 pushState 改變 hash 值,進行目標路由匹配,渲染組件,popstate 監聽瀏覽器操做,完成導航功能,若不支持,使用 location.hash 設置 hash 值,hashchange 監聽 URL 變化完成路由導航。
  • History 模式:地址欄 URL 中沒有 #。與 Hash 模式實現導航的思路是同樣的。不一樣的是,vue-router 提供了 fallback 配置,當瀏覽器不支持 history.pushState 控制路由是否應該回退到 hash 模式。默認值爲 true。

    網上資料對 Hash 路由模式的原理分析大都是經過 location.hash 結合 hashchange 實現,與上述描述的 hash 路由模式的實現方式不一樣,這也是小編最近閱讀 vue-router 源碼發現的,鼓勵小夥伴們讀一下,確定會收穫滿滿!

2. react-router-dom 經常使用的 2 種模式是 browserHistory、hashHistory,直接用 <BrowserRouter> 或 <HashHistory> 將根組件(一般是 <App> )包裹起來就能實現。

  • react-router 的實現依賴 history.js,history.js 是 JavaScript 庫。<BrowserRouter> 、 <HashHistory> 分別基於 history.js 的 BrowserHistory 類、HashHistory 類實現。
  • BrowserHistory 類經過 pushState、replaceState 和 popstate 實現,但並無相似 vue-router 的兼容處理。HashHistory 類則是直接經過 location.hash、location.replace 和 hashchange 實現,沒有優先使用 history 新特性的處理。

嵌套路由與子路由

1. vue-router 嵌套路由

在 new VueRouter() 配置路由表時,經過定義 Children 實現嵌套路由,不管第幾層的路由組件,都會被渲染到父組件 <router-view/> 標識的地方。

router.js

const router = new Router({
    mode:'history',
    routes: [{
        path: '/nest',
        name: 'nest',
        component: Nest,
        children:[{
            path:'first',
            name:'first',
            component:NestFirst
        }]
    }]
})

nest.vue

<div class="nest">
    一級路由 <router-view></router-view>
</div>

first.vue

<div class="nest">
    二級路由 <router-view></router-view>
</div>

/nest 下設置了二級路由 /first,二級對應的組件渲染在一級路由匹配的組件 <router-view/> 標識的地方。在配置子路由時,path 只須要是當前路徑便可。

2. react-router 子路由

react-router 根組件會被渲染到 <Router/> 指定的位置,子路由則會做爲子組件,由父組件指定該對象的渲染位置。若是想要實現上述 vue-router 嵌套的效果,須要這樣設置:

route.js

const Route = () => (
    <HashRouter>
        <Switch>
            <Route path="/nest" component={Nest}/>
        </Switch>
    </HashRouter>
);

nest.js

export default class Nest extends Component {
    render() {
        return (
            <div className="nest">
                一級路由
                <Switch>
                    <Route path="/nest/first" component={NestFirst}/>
                </Switch>
            </div>
        )
    }
}

first.js

export default class NestFirst extends Component {
    render() {
        return (
            <div className="nest">
                二級路由
                <Switch>
                    <Route exact path="/nest/first/second" component={NestSecond}/>
                </Switch>
            </div>
        )
    }
}

其中,/nest 爲一級路由,/fitst 二級路由匹配的組件,做爲一級路由的子組件。react-router 定義子路由 path 時,須要寫完整的路徑,即父路由的路徑要完整。

路由守衛

1. vue-router 導航守衛分爲全局守衛、路由獨享守衛、組件內的守衛三種。主要用來經過跳轉或取消的方式守衛導航。

a. 全局守衛

  • beforeEach — 全局前置鉤子(每一個路由調用前都會觸發,根據 from 和 to 來判斷是哪一個路由觸發)
  • beforeResolve — 全局解析鉤子(和 router.beforeEach 相似,區別是在導航被確認以前,同時在全部組件內守衛和異步路由組件被解析以後,解析守衛就被調用)
  • afterEach — 全局後置鉤子

b. 路由獨享守衛

  • 路由配置上能夠直接定義 beforeEnter 守衛。

c. 組件內守衛

  • beforeRouteEnter — 在渲染該組件的對應路由被 confirm 前調用,不能獲取組件實例 this,由於當守衛執行前,組件實例還沒被建立。
  • beforeRouteUpdate — 當前路由改變,可是該組件被複用時調用
  • beforeRouteLeave — 導航離開該組件的對應路由時調用

2. react-router 4.0 版本以前,提供了 onEnter 和 onLeave 鉤子,實現相似 vue-router 導航守衛的功能,但 4.0 版本後取消了該方法。

路由信息

1. vue-router 中 $router、$route 對象

vue-router 在註冊時,爲每一個 vue 實例注入了 $router、$route 對象。$router 爲 router 實例信息,利用 push 和 replace 方法實現路由跳轉,$route 提供當前激活的路由信息。

import router from './router'
export default new Vue({
    el: '#app',
    router,
    render: h => h(App),
})

2. react-router 中 history、location 對象

在每一個由 <Route/> 包裹的組件中提供了 history、location 對象。利用 this.props.history 的 push、replace 方法實現路由導航,this.props.location 獲取當前激活的路由信息。

const BasicRoute = () => (
    <div>
        <HeaderNav></HeaderNav>
        <HashRouter>
            <Switch>
                <Route exact path="/" component={Home}/>
            </Switch>
        </HashRouter>
    </div>
);

若是想要得到 history、location 必定是 <Route /> 包裹的組件。因此在 <HeaderNav/> 中是沒法獲取這兩個對象的,而 <Home/> 組件是能夠的。

vue-router 是全局配置方式,react-router 是全局組件方式,但二者呈現給開發者的功能其實是大同小異的。固然,vue-router 與 react-router 在使用上的差別不只僅是小編說的這些。說到底,無論用什麼樣的方式實現,前端路由的實現原理都是不會變的。

總結

前端路由的初步體驗立刻就要結束了,在決定深刻研究前端路由以前,小編自信滿滿,感受應該不會花費很大的精力與時間,可事實是,涉及到的知識盲區愈來愈多,信心在逐漸瓦解。好在結局不錯,收穫了不少,也但願《SPA 路由三部曲之核心原理》這篇文章能讓你們有所收穫,哪怕只是一個知識點。

小編已經在爭分奪秒的準備《SPA 路由三部曲之 MyVueRouter 實踐》、《SPA 路由三部曲之 VueRouter 源碼解析》過程當中了,小編相信是不會讓你失望的,請充滿期待吧!

PS:文章中有些是我的觀點,若是不對,歡迎交流、指正!

相關文章
相關標籤/搜索