淺析vue-router實(shí)現(xiàn)原理及兩種模式

之前用Vue開發(fā)單頁應(yīng)用,發(fā)現(xiàn)不管路由怎么變化,瀏覽器地址欄總是會(huì)有一個(gè)'#'號(hào)。

為萬秀等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及萬秀網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè)、萬秀網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

淺析vue-router實(shí)現(xiàn)原理及兩種模式
淺析vue-router實(shí)現(xiàn)原理及兩種模式

當(dāng)時(shí)檢查自己的代碼,沒有發(fā)現(xiàn)請求的地址帶'#',當(dāng)時(shí)也很納悶,但是由于沒有影響頁面的渲染以及向后臺(tái)發(fā)送請求,當(dāng)時(shí)也沒有在意。最近看了一下vue-router的實(shí)現(xiàn)原理,才逐漸揭開了這個(gè)謎題。

vue-router 的兩種方式(瀏覽器環(huán)境下)

1. Hash (對應(yīng)HashHistory)

hash(“#”)符號(hào)的本來作用是加在URL中指示網(wǎng)頁中的位置:

http://www.example.com/index.html#print

#符號(hào)本身以及它后面的字符稱之為hash(也就是我之前為什么地址欄都會(huì)有一個(gè)‘#'),可通過window.location.hash屬性讀取。它具有如下特點(diǎn):

hash雖然出現(xiàn)在URL中,但不會(huì)被包括在HTTP請求中。它是用來指導(dǎo)瀏覽器動(dòng)作的,對

每一次改變hash(window.location.hash),都會(huì)在瀏覽器的訪問歷史中增加一個(gè)記錄

利用hash的以上特點(diǎn),就可以來實(shí)現(xiàn)前端路由“更新視圖但不重新請求頁面”的功能了。

2. History (對應(yīng)HTML5History)

History接口 是瀏覽器歷史記錄棧提供的接口,通過back(), forward(), go()等方法,我們可以讀取瀏覽器歷史記錄棧的信息,進(jìn)行各種跳轉(zhuǎn)操作。

從HTML5開始,History interface提供了兩個(gè)新的方法:pushState(), replaceState()使得我們可以對瀏覽器歷史記錄棧進(jìn)行修改:

window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)

stateObject: 當(dāng)瀏覽器跳轉(zhuǎn)到新的狀態(tài)時(shí),將觸發(fā)popState事件,該事件將攜帶這個(gè)stateObject參數(shù)的副本 title: 所添加記錄的標(biāo)題 URL: 所添加記錄的URL

這兩個(gè)方法有個(gè)共同的特點(diǎn):當(dāng)調(diào)用他們修改瀏覽器歷史記錄棧后,雖然當(dāng)前URL改變了,但瀏覽器不會(huì)刷新頁面,這就為單頁應(yīng)用前端路由“更新視圖但不重新請求頁面”提供了基礎(chǔ)。 瀏覽器歷史記錄可以看作一個(gè)「?!埂J且环N后進(jìn)先出的結(jié)構(gòu),可以把它想象成一摞盤子,用戶每點(diǎn)開一個(gè)新網(wǎng)頁,都會(huì)在上面加一個(gè)新盤子,叫「入?!?。用戶每次點(diǎn)擊「后退」按鈕都會(huì)取走最上面的那個(gè)盤子,叫做「出棧」。而每次瀏覽器顯示的自然是最頂端的盤子的內(nèi)容。

vue-router 的作用

vue-router的作用就是通過改變URL,在不重新請求頁面的情況下,更新頁面視圖。簡單的說就是,雖然地址欄的地址改變了,但是并不是一個(gè)全新的頁面,而是之前的頁面某些部分進(jìn)行了修改。

export default new Router({
 // mode: 'history', //后端支持可開
 routes: constantRouterMap
})

這是Vue項(xiàng)目中常見的一段初始化vue-router的代碼,之前沒仔細(xì)研究過vue-router,不知道還有一個(gè)mode屬性,后來看了相關(guān)文章后了解到,mode屬性用來指定vue-router使用哪一種模式。在沒有指定mode的值,則使用hash模式。

源碼分析

首先看一下vue-router的構(gòu)造函數(shù)

constructor (options: RouterOptions = {}) {
 this.app = null
 this.apps = []
 this.options = options
 this.beforeHooks = []
 this.resolveHooks = []
 this.afterHooks = []
 this.matcher = createMatcher(options.routes || [], this)

 let mode = options.mode || 'hash'
 this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
 if (this.fallback) {
 mode = 'hash'
 }
 if (!inBrowser) {
 mode = 'abstract'
 }
 this.mode = mode

 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': //非瀏覽器環(huán)境下
 this.history = new AbstractHistory(this, options.base) 
 break
 default:
 if (process.env.NODE_ENV !== 'production') {
  assert(false, `invalid mode: ${mode}`)
 }
 }
 }

主要是先獲取mode的值,如果mode的值為 history 但是瀏覽器不支持 history 模式,那么就強(qiáng)制設(shè)置mode值為 hash 。如果支持則為 history 。接下來,根據(jù)mode的值,來選擇vue-router使用哪種模式。

case 'history':
 this.history = new HTML5History(this, options.base)
 break
case 'hash':
 this.history = new HashHistory(this, options.base, this.fallback)
 break

這樣就有了兩種模式。確定好了vue-router使用哪種模式后,就到了init。 先來看看router 的 init 方法就干了哪些事情,在 src/index.js 中

init (app: any /* Vue component instance */) {
// ....
 const history = this.history

 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類暴露的以下方法實(shí)際是調(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)
 }
}

如果是HTML5History,則執(zhí)行

history.transitionTo(history.getCurrentLocation())

如果是Hash模式,則執(zhí)行

const setupHashListener = () => {
 history.setupListeners()
 }
 history.transitionTo(
 history.getCurrentLocation(),
 setupHashListener,
 setupHashListener
 )

可以看出,兩種模式都執(zhí)行了transitionTo( )函數(shù)。 接下來看一下兩種模式分別是怎么執(zhí)行的,首先看一下Hash模式

HashHistory.push()

我們來看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進(jìn)行了直接賦值:

window.location.hash = route.fullPath hash的改變會(huì)自動(dòng)添加到瀏覽器的訪問歷史記錄中。

那么視圖的更新是怎么實(shí)現(xiàn)的呢,我們來看父類History中transitionTo()方法的這么一段:

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
 // 調(diào)用 match 得到匹配的 route 對象
 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
}

可以看到,當(dāng)路由變化時(shí),調(diào)用了History中的this.cb方法,而this.cb方法是通過History.listen(cb)進(jìn)行設(shè)置的?;氐絍ueRouter類定義中,找到了在init()方法中對其進(jìn)行了設(shè)置:

init (app: any /* Vue component instance */) {
 
 this.apps.push(app)

 history.listen(route => {
 this.apps.forEach((app) => {
 app._route = route
 })
 })
}

代碼中的app指的是Vue的實(shí)例,._route本不是本身的組件中定義的內(nèi)置屬性,而是在Vue.use(Router)加載vue-router插件的時(shí)候,通過Vue.mixin()方法,全局注冊一個(gè)混合,影響注冊之后所有創(chuàng)建的每個(gè) Vue 實(shí)例,該混合在beforeCreate鉤子中通過Vue.util.defineReactive()定義了響應(yīng)式的_route。所謂響應(yīng)式屬性,即當(dāng)_route值改變時(shí),會(huì)自動(dòng)調(diào)用Vue實(shí)例的render()方法,更新視圖。vm.render()是根據(jù)當(dāng)前的 _route 的path,name等屬性,來將路由對應(yīng)的組件渲染到. 所以總結(jié)下來,從路由改變到視圖的更新流程如下:

this.$router.push(path)
 --> 
HashHistory.push() 
--> 
History.transitionTo() 
--> 
const route = this.router.match(location, this.current)會(huì)進(jìn)行地址匹配,得到一個(gè)對應(yīng)當(dāng)前地址的route(路由信息對象)
-->
History.updateRoute(route) 
 -->
 app._route=route (Vue實(shí)例的_route改變) 由于_route屬性是采用vue的數(shù)據(jù)劫持,當(dāng)_route的值改變時(shí),會(huì)執(zhí)行響應(yīng)的render( )
-- >
vm.render() 具體是在<router-view></router-view> 中render
 -->
window.location.hash = route.fullpath (瀏覽器地址欄顯示新的路由的path)

HashHistory.replace()

說完了HashHistory.push(),該說HashHistory.replace()了。

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
 )
}

可以看出來,HashHistory.replace它與push()的實(shí)現(xiàn)結(jié)構(gòu)上基本相似,不同點(diǎn)在于它不是直接對window.location.hash進(jìn)行賦值,而是調(diào)用window.location.replace方法將路由進(jìn)行替換。這樣不會(huì)將新路由添加到瀏覽器訪問歷史的棧頂,而是替換掉當(dāng)前的路由。

監(jiān)聽地址欄

可以看出來,上面的過程都是在代碼內(nèi)部進(jìn)行路由的改變的,比如項(xiàng)目中常見的this.$router.push(), 等方法。然后將瀏覽器的地址欄置為新的hash值。那么如果直接在地址欄中輸入U(xiǎn)RL從而改變路由呢,例如

淺析vue-router實(shí)現(xiàn)原理及兩種模式

我將dashboadr刪除,然后置為article/hotSpot,然后回車,vue又是如何處理的呢?

setupListeners () {
 window.addEventListener('hashchange', () => {
 if (!ensureSlash()) {
 return
 }
 this.transitionTo(getHash(), route => {
 replaceHash(route.fullPath)
 })
 })
}

該方法設(shè)置監(jiān)聽了瀏覽器事件hashchange,調(diào)用的函數(shù)為replaceHash,即在瀏覽器地址欄中直接輸入路由相當(dāng)于代碼調(diào)用了replace()方法.后面的步驟自然與HashHistory.replace()相同,一樣實(shí)現(xiàn)頁面渲染。

HTML5History

HTML5History模式的vue-router 代碼結(jié)構(gòu)以及更新視圖的邏輯與hash模式基本類似,和HashHistory的步驟基本一致,只是HashHistory的push和replace()變成了HTML5History.pushState()和HTML5History.replaceState()

在HTML5History中添加對修改瀏覽器地址欄URL的監(jiān)聽是直接在構(gòu)造函數(shù)中執(zhí)行的,對HTML5History的popstate 事件進(jìn)行監(jiān)聽:

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)
 }
 })
 })
}

以上就是vue-router hash模式與history模式不同模式下處理邏輯的分析了。

總結(jié)

以上所述是小編給大家介紹的vue-router實(shí)現(xiàn)原理及兩種模式分析,希望對大家有所幫助!

網(wǎng)站欄目:淺析vue-router實(shí)現(xiàn)原理及兩種模式
本文路徑:http://muchs.cn/article40/joodeo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司面包屑導(dǎo)航、手機(jī)網(wǎng)站建設(shè)微信小程序、網(wǎng)站制作、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

猜你還喜歡下面的內(nèi)容

成都網(wǎng)頁設(shè)計(jì)公司

微信小程序知識(shí)

同城分類信息