摘要:方法與方法不同之處在于,它并不是將新路由添加到瀏覽器訪問歷史棧頂,而是替換掉當前的路由可以看出,它與的實現(xiàn)結(jié)構(gòu)基本相似,不同點它不是直接對進行賦值,而是調(diào)用方法將路由進行替換。
隨著前端應用的業(yè)務功能起來越復雜,用戶對于使用體驗的要求越來越高,單面(SPA)成為前端應用的主流形式。大型單頁應用最顯著特點之一就是采用的前端路由系統(tǒng),通過改變URL,在不重新請求頁面的情況下,更新頁面視圖。
更新視圖但不重新請求頁面,是前端路由原理的核心之一,目前在瀏覽器環(huán)境中這一功能的實現(xiàn)主要有2種方式:
利用URL中的hash("#");
利用History interface在HTML5中新增的方法;
vue-router是Vue.js框架的路由插件,它是通過mode這一參數(shù)控制路由的實現(xiàn)模式的:
const router=new VueRouter({ mode:"history", routes:[...] })
創(chuàng)建VueRouter的實例對象時,mode以構(gòu)造參數(shù)的形式傳入。
src/index.js export default class VueRouter{ mode: string; // 傳入的字符串參數(shù),指示history類別 history: HashHistory | HTML5History | AbstractHistory; // 實際起作用的對象屬性,必須是以上三個類的枚舉 fallback: boolean; // 如瀏覽器不支持,"history"模式需回滾為"hash"模式 constructor (options: RouterOptions = {}) { let mode = options.mode || "hash" // 默認為"hash"模式 this.fallback = mode === "history" && !supportsPushState // 通過supportsPushState判斷瀏覽器是否支持"history"模式 if (this.fallback) { mode = "hash" } if (!inBrowser) { mode = "abstract" // 不在瀏覽器環(huán)境下運行需強制為"abstract"模式 } this.mode = mode // 根據(jù)mode確定history實際的類并實例化 switch (mode) { case "history": this.history = new HTML5History(this, options.base) break case "hash": this.history = new HashHistory(this, options.base, this.fallback) break case "abstract": this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== "production") { assert(false, `invalid mode: ${mode}`) } } } init (app: any /* Vue component instance */) { const history = this.history // 根據(jù)history的類別執(zhí)行相應的初始化操作和監(jiān)聽 if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() } history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } history.listen(route => { this.apps.forEach((app) => { app._route = route }) }) } // VueRouter類暴露的以下方法實際是調(diào)用具體history對象的方法 push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.push(location, onComplete, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.replace(location, onComplete, onAbort) } }
作為參數(shù)傳入的字符串屬性mode只是一個標記,用來指示實際起作用的對象屬性history的實現(xiàn)類,兩者對應關(guān)系:
modehistory: "history":HTML5History; "hash":HashHistory; "abstract":AbstractHistory;
在初始化對應的history之前,會對mode做一些校驗:若瀏覽器不支持HTML5History方式(通過supportsPushState變量判斷),則mode設(shè)為hash;若不是在瀏覽器環(huán)境下運行,則mode設(shè)為abstract;
VueRouter類中的onReady(),push()等方法只是一個代理,實際是調(diào)用的具體history對象的對應方法,在init()方法中初始化時,也是根據(jù)history對象具體的類別執(zhí)行不同操作
HashHistoryhash("#")符號的本來作用是加在URL指示網(wǎng)頁中的位置:
http://www.example.com/index.html#print
#本身以及它后面的字符稱之為hash可通過window.location.hash屬性讀取.
hash雖然出現(xiàn)在url中,但不會被包括在http請求中,它是用來指導瀏覽器動作的,對服務器端完全無用,因此,改變hash不會重新加載頁面。
可以為hash的改變添加監(jiān)聽事件:
window.addEventListener("hashchange",funcRef,false)
每一次改變hash(window.location.hash),都會在瀏覽器訪問歷史中增加一個記錄。
利用hash的以上特點,就可以來實現(xiàn)前端路由"更新視圖但不重新請求頁面"的功能了。
HashHistory.push()push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { pushHash(route.fullPath) onComplete && onComplete(route) }, onAbort) } function pushHash (path) { window.location.hash = path }
transitionTo()方法是父類中定義的是用來處理路由變化中的基礎(chǔ)邏輯的,push()方法最主要的是對window的hash進行了直接賦值:
window.location.hash=route.fullPath
hash的改變會自動添加到瀏覽器的訪問歷史記錄中。
那么視圖的更新是怎么實現(xiàn)的呢,我們來看看父類History中的transitionTo()方法:
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { const route = this.router.match(location, this.current) this.confirmTransition(route, () => { this.updateRoute(route) ... }) } updateRoute (route: Route) { this.cb && this.cb(route) } listen (cb: Function) { this.cb = cb }
可以看到,當路由變化時,調(diào)用了Hitory中的this.cb方法,而this.cb方法是通過History.listen(cb)進行設(shè)置的,回到VueRouter類定義中,找到了在init()中對其進行了設(shè)置:
init (app: any /* Vue component instance */) { this.apps.push(app) history.listen(route => { this.apps.forEach((app) => { app._route = route }) }) }
app為Vue組件實例,但是Vue作為漸進式的前端框架,本身的組件定義中應該是沒有有關(guān)路由內(nèi)置屬性_route,如果組件中要有這個屬性,應該是在插件加載的地方,即VueRouter的install()方法中混入Vue對象的,install.js的源碼:
export function install (Vue) { Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, "_route", this._router.history.current) } registerInstance(this, this) }, }) }
通過Vue.mixin()方法,全局注冊一個混合,影響注冊之后所有創(chuàng)建的每個Vue實例,該混合在beforeCreate鉤子中通過Vue.util.defineReactive()定義了響應式的_route屬性。所謂響應式屬性,即當_route值改變時,會自動調(diào)用Vue實例的render()方法,更新視圖。
$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()HashHistory.replace()
replace()方法與push()方法不同之處在于,它并不是將新路由添加到瀏覽器訪問歷史棧頂,而是替換掉當前的路由:
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { replaceHash(route.fullPath) onComplete && onComplete(route) }, onAbort) } function replaceHash (path) { const i = window.location.href.indexOf("#") window.location.replace( window.location.href.slice(0, i >= 0 ? i : 0) + "#" + path ) }
可以看出,它與push()的實現(xiàn)結(jié)構(gòu)基本相似,不同點它不是直接對window.location.hash進行賦值,而是調(diào)用window.location.replace方法將路由進行替換。
監(jiān)聽地址欄上面的VueRouter.push()和VueRouter.replace()是可以在vue組件的邏輯代碼中直接調(diào)用的,除此之外在瀏覽器中,用戶還可以直接在瀏覽器地址欄中輸入改變路由,因此還需要監(jiān)聽瀏覽器地址欄中路由的變化 ,并具有與通過代碼調(diào)用相同的響應行為,在HashHistory中這一功能通過setupListeners監(jiān)聽hashchange實現(xiàn):
setupListeners () { window.addEventListener("hashchange", () => { if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { replaceHash(route.fullPath) }) }) }
該方法設(shè)置監(jiān)聽了瀏覽器事件hashchange,調(diào)用的函數(shù)為replaceHash,即在瀏覽器地址欄中直接輸入路由相當于代碼調(diào)用了replace()方法。
HTML5HistoryHistory interface是瀏覽器歷史記錄棧提供的接口,通過back(),forward(),go()等方法,我們可以讀取瀏覽器歷史記錄棧的信息,進行各種跳轉(zhuǎn)操作。
從HTML5開始,History interface提供了2個新的方法:pushState(),replaceState()使得我們可以對瀏覽器歷史記錄棧進行修改:
window.history.pushState(stateObject,title,url) window.history,replaceState(stateObject,title,url)
stateObject:當瀏覽器跳轉(zhuǎn)到新的狀態(tài)時,將觸發(fā)popState事件,該事件將攜帶這個stateObject參數(shù)的副本
title:所添加記錄的標題
url:所添加記錄的url
這2個方法有個共同的特點:當調(diào)用他們修改瀏覽器歷史棧后,雖然當前url改變了,但瀏覽器不會立即發(fā)送請求該url,這就為單頁應用前端路由,更新視圖但不重新請求頁面提供了基礎(chǔ)。
push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { pushState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { replaceState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } // src/util/push-state.js export function pushState (url?: string, replace?: boolean) { saveScrollPosition() // try...catch the pushState call to get around Safari // DOM Exception 18 where it limits to 100 pushState calls const history = window.history try { if (replace) { history.replaceState({ key: _key }, "", url) } else { _key = genKey() history.pushState({ key: _key }, "", url) } } catch (e) { window.location[replace ? "replace" : "assign"](url) } } export function replaceState (url?: string) { pushState(url, true) }
代碼結(jié)構(gòu)以及更新視圖的邏輯與hash模式基本類似,只不過將對window.location.hash()直接進行賦值window.location.replace()改為了調(diào)用history.pushState()和history.replaceState()方法。
在HTML5History中添加對修改瀏覽器地址欄URL的監(jiān)聽popstate是直接在構(gòu)造函數(shù)中執(zhí)行的:
constructor (router: Router, base: ?string) { window.addEventListener("popstate", e => { const current = this.current this.transitionTo(getLocation(this.base), route => { if (expectScroll) { handleScroll(router, route, current, true) } }) }) }
HTML5History用到了HTML5的新特性,需要瀏版本的支持,通過supportsPushState來檢查:
src/util/push-state.js export const supportsPushState = inBrowser && (function () { const ua = window.navigator.userAgent if ( (ua.indexOf("Android 2.") !== -1 || ua.indexOf("Android 4.0") !== -1) && ua.indexOf("Mobile Safari") !== -1 && ua.indexOf("Chrome") === -1 && ua.indexOf("Windows Phone") === -1 ) { return false } return window.history && "pushState" in window.history })()
以上就是hash模式與history模式源碼導讀,這2種模式都是通過瀏覽器接口實現(xiàn)的,除此之外,vue-router還為非瀏覽器環(huán)境準備了一個abstract模式,其原理為用一個數(shù)組stack模擬出瀏覽器歷史記錄棧的功能。
兩種模式比較一般的需求場景中,hash模式與history模式是差不多的,根據(jù)MDN的介紹,調(diào)用history.pushState()相比于直接修改hash主要有以下優(yōu)勢:
pushState設(shè)置的新url可以是與當前url同源的任意url,而hash只可修改#后面的部分,故只可設(shè)置與當前同文檔的url
pushState設(shè)置的新url可以與當前url一模一樣,這樣也會把記錄添加到棧中,而hash設(shè)置的新值必須與原來不一樣才會觸發(fā)記錄添加到棧中
pushState通過stateObject可以添加任意類型的數(shù)據(jù)記錄中,而hash只可添加短字符串
pushState可額外設(shè)置title屬性供后續(xù)使用
history模式的問題對于單頁應用來說,理想的使用場景是僅在進入應用時加載index.html,后續(xù)在的網(wǎng)絡(luò)操作通過ajax完成,不會根據(jù)url重新請求頁面,但是如果用戶直接在地址欄中輸入并回車,瀏覽器重啟重新加載等特殊情況。
hash模式僅改變hash部分的內(nèi)容,而hash部分是不會包含在http請求中的(hash帶#):
http://oursite.com/#/user/id //如請求,只會發(fā)送http://oursite.com/
所以hash模式下遇到根據(jù)url請求頁面不會有問題
而history模式則將url修改的就和正常請求后端的url一樣(history不帶#)
http://oursite.com/user/id
如果這種向后端發(fā)送請求的話,后端沒有配置對應/user/id的get路由處理,會返回404錯誤。
官方推薦的解決辦法是在服務端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態(tài)資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。同時這么做以后,服務器就不再返回 404 錯誤頁面,因為對于所有路徑都會返回 index.html 文件。為了避免這種情況,在 Vue 應用里面覆蓋所有的路由情況,然后在給出一個 404 頁面?;蛘撸绻怯?Node.js 作后臺,可以使用服務端的路由來匹配 URL,當沒有匹配到路由的時候返回 404,從而實現(xiàn) fallback。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/94890.html
摘要:后端路由簡介路由這個概念最先是后端出現(xiàn)的。前端路由模式隨著的流行,異步數(shù)據(jù)請求交互運行在不刷新瀏覽器的情況下進行。通過這些就能用另一種方式來實現(xiàn)前端路由了,但原理都是跟實現(xiàn)相同的。 后端路由簡介 路由這個概念最先是后端出現(xiàn)的。在以前用模板引擎開發(fā)頁面時,經(jīng)常會看到這樣 http://www.xxx.com/login 大致流程可以看成這樣: 瀏覽器發(fā)出請求 服務器監(jiān)聽到80端口(或4...
摘要:如何實現(xiàn)前端路由要實現(xiàn)前端路由,需要解決兩個核心如何改變卻不引起頁面刷新如何檢測變化了下面分別使用和兩種實現(xiàn)方式回答上面的兩個核心問題。 原文鏈接:github.com/whinc/blog/… 在單頁應用如此流行的今天,曾經(jīng)令人驚嘆的前端路由已經(jīng)成為各大框架的基礎(chǔ)標配,每個框架都提供了強大的路由功能,導致路由實現(xiàn)變的復雜。想要搞懂路由內(nèi)部實現(xiàn)還是有些困難的,但是如果只想了解路由實現(xiàn)基本...
摘要:我們知道是的核心插件,而當前項目一般都是單頁面應用,也就是說是應用在單頁面應用中的。原理是傳統(tǒng)的頁面應用,是用一些超鏈接來實現(xiàn)頁面切換和跳轉(zhuǎn)的其實剛才單頁面應用跳轉(zhuǎn)原理即實現(xiàn)原理實現(xiàn)原理原理核心就是更新視圖但不重新請求頁面。 近期面試,遇到關(guān)于vue-router實現(xiàn)原理的問題,在查閱了相關(guān)資料后,根據(jù)自己理解,來記錄下。我們知道vue-router是vue的核心插件,而當前vue項目...
摘要:如果要相應狀態(tài)改變,通常最好使用計算屬性或取而代之。那解決問題的思路便是在改變的情況下,保證頁面的不刷新。后面值的變化,并不會導致瀏覽器向服務器發(fā)出請求,瀏覽器不發(fā)出請求,也就不會刷新頁面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...
摘要:如果要相應狀態(tài)改變,通常最好使用計算屬性或取而代之。那解決問題的思路便是在改變的情況下,保證頁面的不刷新。后面值的變化,并不會導致瀏覽器向服務器發(fā)出請求,瀏覽器不發(fā)出請求,也就不會刷新頁面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...
摘要:如果要相應狀態(tài)改變,通常最好使用計算屬性或取而代之。那解決問題的思路便是在改變的情況下,保證頁面的不刷新。后面值的變化,并不會導致瀏覽器向服務器發(fā)出請求,瀏覽器不發(fā)出請求,也就不會刷新頁面。 1.vue生命周期2.vue 雙向綁定原理3.vue router原理4.vue router動態(tài)路由 1.vue 生命周期鉤子 showImg(https://segmentfault.com/...
閱讀 2633·2019-08-30 10:53
閱讀 3233·2019-08-29 16:20
閱讀 2992·2019-08-29 15:35
閱讀 1818·2019-08-29 12:24
閱讀 2908·2019-08-28 18:19
閱讀 1899·2019-08-23 18:07
閱讀 2388·2019-08-23 15:31
閱讀 1232·2019-08-23 14:05