摘要:目前前端三杰都推介單頁面應用開發(fā)模式,在路由切換時替換中最小修改的部分,來減少原先因為多頁應用的頁面跳轉帶來的巨量性能損耗。
目前前端三杰 Angular、React、Vue 都推介單頁面應用 SPA 開發(fā)模式,在路由切換時替換 DOM Tree 中最小修改的部分 DOM,來減少原先因為多頁應用的頁面跳轉帶來的巨量性能損耗。它們都有自己的典型路由解決方案,@angular/router、react-router、vue-router。
一般來說,這些路由插件總是提供兩種不同方式的路由方式: Hash 和 History,有時也會提供非瀏覽器環(huán)境下的路由方式 Abstract,在 vue-router 中是使用了外觀模式將幾種不同的路由方式提供了一個一致的高層接口,讓我們可以更解耦的在不同路由方式中切換。
值得一提的是,Hash 和 History 除了外觀上的不同之外,還一個區(qū)別是:Hash 方式的狀態(tài)保存需要另行傳遞,而 HTML5 History 原生提供了自定義狀態(tài)傳遞的能力,我們可以直接利用其來傳遞信息。
下面我們具體看看這兩種方式都有哪些特點,并提供簡單的實現(xiàn),比如基本的功能,更復雜的功能比如懶加載、動態(tài)路徑匹配、嵌套路由、路由別名等等,可以關注一下后面的 vue-router 源碼解讀方面的博客。
1. Hash 1.1 相關 ApiHash 方法是在路由中帶有一個 #,主要原理是通過監(jiān)聽 # 后的 URL 路徑標識符的更改而觸發(fā)的瀏覽器 hashchange 事件,然后通過獲取 location.hash 得到當前的路徑標識符,再進行一些路由跳轉的操作,參見 MDN
location.href:返回完整的 URL
location.hash:返回 URL 的錨部分
location.pathname:返回 URL 路徑名
hashchange 事件:當 location.hash 發(fā)生改變時,將觸發(fā)這個事件
比如訪問一個路徑 http://sherlocked93.club/base/#/page1,那么上面幾個值分別為:
# http://sherlocked93.club/base/#/page1 { "href": "http://sherlocked93.club/base/#/page1", "pathname": "/base/", "hash": "#/page1" }
注意: Hash 方法是利用了相當于頁面錨點的功能,所以與原來的通過錨點定位來進行頁面滾動定位的方式沖突,導致定位到錯誤的路由路徑,因此需要采用別的辦法,之前在寫 progress-catalog 這個插件碰到了這個情況。
1.2 實例這里簡單做一個實現(xiàn),原理是把目標路由和對應的回調記錄下來,點擊跳轉觸發(fā) hashchange 的時候獲取當前路徑并執(zhí)行對應回調,效果:
class RouterClass { constructor() { this.routes = {} // 記錄路徑標識符對應的cb this.currentUrl = "" // 記錄hash只為方便執(zhí)行cb window.addEventListener("load", () => this.render()) window.addEventListener("hashchange", () => this.render()) } /* 初始化 */ static init() { window.Router = new RouterClass() } /* 注冊路由和回調 */ route(path, cb) { this.routes[path] = cb || function() {} } /* 記錄當前hash,執(zhí)行cb */ render() { this.currentUrl = location.hash.slice(1) || "/" this.routes[this.currentUrl]() } }
具體實現(xiàn)參照 CodePen
如果希望使用腳本來控制 Hash 路由的后退,可以將經歷的路由記錄下來,路由后退跳轉的實現(xiàn)是對 location.hash 進行賦值。但是這樣會引發(fā)重新引發(fā) hashchange 事件,第二次進入 render 。所以我們需要增加一個標志位,來標明進入 render 方法是因為回退進入的還是用戶跳轉
class RouterClass { constructor() { this.isBack = false this.routes = {} // 記錄路徑標識符對應的cb this.currentUrl = "" // 記錄hash只為方便執(zhí)行cb this.historyStack = [] // hash棧 window.addEventListener("load", () => this.render()) window.addEventListener("hashchange", () => this.render()) } /* 初始化 */ static init() { window.Router = new RouterClass() } /* 記錄path對應cb */ route(path, cb) { this.routes[path] = cb || function() {} } /* 入棧當前hash,執(zhí)行cb */ render() { if (this.isBack) { // 如果是由backoff進入,則置false之后return this.isBack = false // 其他操作在backoff方法中已經做了 return } this.currentUrl = location.hash.slice(1) || "/" this.historyStack.push(this.currentUrl) this.routes[this.currentUrl]() } /* 路由后退 */ back() { this.isBack = true this.historyStack.pop() // 移除當前hash,回退到上一個 const { length } = this.historyStack if (!length) return let prev = this.historyStack[length - 1] // 拿到要回退到的目標hash location.hash = `#${ prev }` this.currentUrl = prev this.routes[prev]() // 執(zhí)行對應cb } }
代碼實現(xiàn)參考 CodePen
2. HTML5 History Api 2.1 相關 ApiHTML5 提供了一些路由操作的 Api,關于使用可以參看
history.go(n):路由跳轉,比如n為 2 是往前移動2個頁面,n為 -2 是向后移動2個頁面,n為0是刷新頁面
history.back():路由后退,相當于 history.go(-1)
history.forward():路由前進,相當于 history.go(1)
history.pushState():添加一條路由歷史記錄,如果設置跨域網址則報錯
history.replaceState():替換當前頁在路由歷史記錄的信息
popstate 事件:當活動的歷史記錄發(fā)生變化,就會觸發(fā) popstate 事件,在點擊瀏覽器的前進后退按鈕或者調用上面前三個方法的時候也會觸發(fā),參見 MDN
2.2 實例將之前的例子改造一下,在需要路由跳轉的地方使用 history.pushState 來入棧并記錄 cb,前進后退的時候監(jiān)聽 popstate 事件拿到之前傳給 pushState 的參數并執(zhí)行對應 cb,因為借用了瀏覽器自己的 Api,因此代碼看起來整潔不少
class RouterClass { constructor(path) { this.routes = {} // 記錄路徑標識符對應的cb history.replaceState({ path }, null, path) // 進入狀態(tài) this.routes[path] && this.routes[path]() window.addEventListener("popstate", e => { const path = e.state && e.state.path this.routes[path] && this.routes[path]() }) } /* 初始化 */ static init() { window.Router = new RouterClass(location.pathname) } /* 注冊路由和回調 */ route(path, cb) { this.routes[path] = cb || function() {} } /* 跳轉路由,并觸發(fā)路由對應回調 */ go(path) { history.pushState({ path }, null, path) this.routes[path] && this.routes[path]() } }
Hash 模式是使用 URL 的 Hash 來模擬一個完整的 URL,因此當 URL 改變的時候頁面并不會重載。History 模式則會直接改變 URL,所以在路由跳轉的時候會丟失一些地址信息,在刷新或直接訪問路由地址的時候會匹配不到靜態(tài)資源。因此需要在服務器上配置一些信息,讓服務器增加一個覆蓋所有情況的候選資源,比如跳轉 index.html 什么的,一般來說是你的 app 依賴的頁面,事實上 vue-router 等庫也是這么推介的,還提供了常見的服務器配置。
代碼實現(xiàn)參考 CodePen
網上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學習過程中的總結,如果發(fā)現(xiàn)錯誤,歡迎留言指出~
參考:
history | MDN
hashchange | MDN
Manipulating the browser history | MDN
前端路由的基本原理 - 大史不說話
History 對象 -- JavaScript 標準參考教程
PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~
另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101490.html
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質gitHub開源練手項目: ...
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質gitHub開源練手項目: ...
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫,利用他們構建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉頁面,刷新整個頁面了,那么他們的原理是什么呢? 優(yōu)質gitHub開源練手項目: ...
摘要:實例中,可追蹤數據發(fā)生變化時,會開啟一個隊列,把變化記錄其中,在下一次事件循環(huán)前,進行去重優(yōu)化,然后重新渲染。最早通過實現(xiàn)了這一需求,通過事件可監(jiān)聽的變化,實現(xiàn)不同頁面的操作。過濾器的使用通過引入中 1、vue中的過渡、動畫效果 單組件()v-enter,v-enter-to,v-enter-active,v-leave,v-leave-to,v-leave-active六種狀態(tài)。(定...
閱讀 1867·2019-08-29 16:44
閱讀 2172·2019-08-29 16:30
閱讀 780·2019-08-29 15:12
閱讀 3530·2019-08-26 10:48
閱讀 2659·2019-08-23 18:33
閱讀 3778·2019-08-23 17:01
閱讀 1943·2019-08-23 15:54
閱讀 1302·2019-08-23 15:05