摘要:如果我們不掉用守衛(wèi)中的,迭代器的肯定并不會執(zhí)行,守衛(wèi)的迭代就停止了,守衛(wèi)堵塞并不會執(zhí)行完畢,也就不會由后面的更細路由操作了。
vue-router導航守衛(wèi)
在本期文章中,我將為大家梳理弄明白以下幾個事情,
1:導航守衛(wèi)的執(zhí)行順序是怎么樣的? 2:導航守衛(wèi)中的next的用處? 3:為什么afterEach守衛(wèi)沒有next? 4:beforeEach是否可以疊加? 5:路由跳轉經(jīng)歷了哪幾部分?在之前說過的一個內(nèi)容router實例的history屬性幫助我們做了所有跳轉部分的事情,所以導航守衛(wèi)的內(nèi)容也在history中。
我們以HTML5History這個類來看一下這個push方法,
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) }
push(我們跳轉時的$router.push就是這個方法)過程中調(diào)用了transitionTo完成了一系列的跳轉內(nèi)容,但這個方法在HTML5的類中并不存在,繼承于base.js類中的方法
transitionTo就是實現(xiàn)路由跳轉的方法
transitionTo的主流程是由confirmTranstion方法于uodateRoute方法結合起來的,翻譯成普通話:路由跳轉要先經(jīng)過一個確認跳轉的過程,在確認過程完成后進行一次路由的更新操作,
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { // 獲取要跳轉的并且經(jīng)過處理的路由 const route = this.router.match(location, this.current) // confirmTranstion確認跳轉過程 this.confirmTransition(route, () => { // 確認完畢后完成更新路由操作 this.updateRoute(route) onComplete && onComplete(route) this.ensureURL() // fire ready cbs once if (!this.ready) { this.ready = true this.readyCbs.forEach(cb => { cb(route) }) } }, err => { if (onAbort) { onAbort(err) } if (err && !this.ready) { this.ready = true this.readyErrorCbs.forEach(cb => { cb(err) }) } }) }
confirmTransiton做了什么呢?首先判斷一下你是不是相同的路由。如果是那就什么都不做,第二步呢,我們要開始收集一波守衛(wèi)了,然后把守衛(wèi)收集起來,然后把每個守衛(wèi)執(zhí)行一遍,confirmTransition就算執(zhí)行成功了。
下面是部分源碼截圖:這個過程中的難點是什么?
如何收集守衛(wèi)組合成守衛(wèi)隊列? 如何按順序執(zhí)行守衛(wèi)的同時可以隨時中止守衛(wèi)隊列? 如何找到守衛(wèi)隊列執(zhí)行完畢后的那個節(jié)點(守衛(wèi)隊列執(zhí)行完可以通知一下)在vue-router中封裝了一個runQueue函數(shù)來解決上面的三個問題的后兩個。第一個問題呢則涉及到vue-router處理路由的一個大篇章,我們著重講一下runQueue函數(shù)
runQueue函數(shù)的思想:1:迭代器模式來保證遍歷隊列時每一步都是可控的,
2:隊列完成后執(zhí)行對應的回調(diào)函數(shù),
推斷出函數(shù)參數(shù)的對應功能:
queue : 需要執(zhí)行的守衛(wèi)隊列
fn : 迭代器函數(shù),守衛(wèi)隊列的每一個守衛(wèi)都去執(zhí)行迭代器函數(shù)
fn的第二個參數(shù)使迭代器進入下一步,不掉用就不會進入下一步(很重點)cb : 結束時調(diào)用的回調(diào)函數(shù)
export function runQueue (queue: Array, fn: Function, cb: Function) { const step = index => { // 隊列里已經(jīng)沒有內(nèi)容可以執(zhí)行了,那就代表隊列執(zhí)行完成了 if (index >= queue.length) { cb() } else { // 如果隊列內(nèi)容存在就執(zhí)行迭代函數(shù) if (queue[index]) { fn(queue[index], () => { step(index + 1) }) // 什么也沒有那就到下一步了 } else { step(index + 1) } } } // 啟動了 step(0) }
runQueue是怎么幫助我們解決守衛(wèi)隊列處理的問題就算說完了。
(快留起來,這函數(shù)簡直吊極了?。?/b>處理守衛(wèi)隊列的大錘子我們已經(jīng)制造好了,可以開工了,那你的守衛(wèi)隊列呢??
對對對,還有守衛(wèi)隊列要收集。
這個時候我們要想想有哪些守衛(wèi)?
前置守衛(wèi):
全局的前置守衛(wèi): beforeEach beforeResolve
路由獨享的守衛(wèi): beforeEnter
組件內(nèi)的守衛(wèi): beforeRouterEnter、beforeRouterUpdate、beforeRouteLeave
后置守衛(wèi):
全局的后置守衛(wèi): afterEach
我們要想一下這些守衛(wèi)都是怎么注冊的,
在路由實例注冊的:
beforeEach、beforeResolve、afterEach
在路由配置中注冊的(路由獨享守衛(wèi)):
beforeEnter
組件內(nèi)的路由守衛(wèi):
beforeRouteLeave、beforeRouteUpdate、beforeRouteEnter
好了我們要去榨取對應的守衛(wèi)了,
confirmTransition的守衛(wèi)分為兩個隊列:我們先來看第一個隊列
// 拿到路由跳轉中更新、摧毀、激活時對應展示的組件。 const { updated, deactivated, activated } = resolveQueue(this.current.matched, route.matched) // 路由守衛(wèi) const queue: Array = [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks(updated), // in-config enter guards activated.map(m => m.beforeEnter), // async components resolveAsyncComponents(activated) )
一個queue的順序:
拿到被摧毀的組件的,榨取出所有組件內(nèi)的離開守衛(wèi)。
全局的beforeEach組件。
拿到更新的所有組件,榨取出所有組件內(nèi)的更新守衛(wèi)。
遍歷要進入的路由,拿到所有路由的獨享守衛(wèi)。
加載要被激活的異步組件
7個守衛(wèi)中的4個守衛(wèi)都在被按順序拿出來了,放入第一個queue。
再下一步要有一個處理守衛(wèi)的迭代器:
我們該如何處理守衛(wèi)?保證在守衛(wèi)中可以停止并且跳轉到其余路由,
保證守衛(wèi)可以正常通過,
const iterator = (hook: NavigationGuard, next) => { if (this.pending !== route) { return abort() } try { hook(route, current, (to: any) => { // 傳個false就直接執(zhí)行路由的錯誤處理,然后停止什么都不做。 if (to === false || isError(to)) { // next(false) -> abort navigation, ensure current URL this.ensureURL(true) abort(to) } else if ( // 如果我們接受了一個可以操作的路徑。 typeof to === "string" || (typeof to === "object" && ( typeof to.path === "string" || typeof to.name === "string" )) ) { // next("/") or next({ path: "/" }) -> redirect abort() // 我們就執(zhí)行路由跳轉操作,并且守衛(wèi)隊列停止下面的迭代 if (typeof to === "object" && to.replace) { this.replace(to) } else { this.push(to) } } else { // confirm transition and pass on the value // 接續(xù)迭代下去咯 next(to) } }) } catch (e) { abort(e) } }
next函數(shù),之前在將runQueue的函數(shù)的時候,fn接收第二個參數(shù)(之前畫過重點),第二個參數(shù)的回調(diào)函數(shù)是完成迭代器向下一步執(zhí)行的功能。
下面會有一點亂:
所有的前置守衛(wèi)都接收三個參數(shù)
beforeEnter(to,from,next)=>{ //這個next就是我們看到的 hook里面接收的箭頭函數(shù)((to:any)=>{}) //這個箭頭函數(shù)里面對迭代器的next進行了一下掉用, //保證在一定情況下迭代器可以向下走一步。 next("/index") // 我們在這種next("/index")傳遞一個可以執(zhí)行的路徑時,(to:any)=>{} //這個箭頭函數(shù)并不會調(diào)用迭代的next,而是跳轉別的路徑執(zhí)行了push操作。 // 如果我們不掉用守衛(wèi)中的next,迭代器的next肯定并不會執(zhí)行,守衛(wèi)的迭代就停止了, // 守衛(wèi)堵塞confirmTransition并不會執(zhí)行完畢,也就不會由后面的更細路由操作了。 }
runQueue(queue, iterator, () => { const postEnterCbs = [] const isValid = () => this.current === route // wait until async components are resolved before // extracting in-component enter guards const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid) const queue = enterGuards.concat(this.router.resolveHooks) runQueue(queue, iterator, () => { if (this.pending !== route) { return abort() } this.pending = null onComplete(route) if (this.router.app) { this.router.app.$nextTick(() => { postEnterCbs.forEach(cb => { cb() }) }) } }) })
我們在把第一個queue(四個守衛(wèi)與一個異步組件的加載)執(zhí)行完畢后,要收集與執(zhí)行第二個queue了,
第二個queue:
收集了被的激活組件內(nèi)的進入守衛(wèi)
全局的beforeResolve的守衛(wèi)
收集完開始執(zhí)行第二個queue的迭代。第二個queue執(zhí)行完執(zhí)行一下onComplete函數(shù),代表著confirmTransition方法執(zhí)行完畢了。確認路由的過程結束了,
下面就是updateRoute的過程。updateRoute的時候執(zhí)行全部的后置守衛(wèi),因為更新路由之后,當前的路由已經(jīng)變化了,所以在給守衛(wèi)傳參數(shù)的時候緩存了一下,之前的路由。
updateRoute (route: Route) { const prev = this.current this.current = route this.cb && this.cb(route) this.router.afterHooks.forEach(hook => { hook && hook(route, prev) }) }
所以為什么afterEach沒有next呢?因為afterEach根本不在迭代器之內(nèi),他就沒有next來觸發(fā)迭代器的下一步。
最后我們說一下beforeEach的內(nèi)容:
我們設置beforeEach全局守衛(wèi)的時候,守衛(wèi)們存儲在哪里?
beforeEach (fn: Function): Function { return registerHook(this.beforeHooks, fn) } function registerHook (list: Array, fn: Function): Function { list.push(fn) // 返回值是一個function return () => { const i = list.indexOf(fn) if (i > -1) list.splice(i, 1) } }
這段代碼beforeEach是通過注冊守衛(wèi)的方式,將注冊的全局前置守衛(wèi)放在beforeHooks的容器內(nèi),這個容器里面裝載著所有的前置守衛(wèi)
一家人(全局的 前置進入、前置resolve、后置守衛(wèi))整整齊齊的放在對應的容器里面,容器是個數(shù)組,所以注冊全局守衛(wèi)的時候,是支持注冊多個的,
router.beforeEach(()=>{xxx}); router.beforeEach(()=>{yyy}); // 這兩個守衛(wèi)都會執(zhí)行,只是先注冊的先執(zhí)行, // registerHook這個方法還可以清除對應的守衛(wèi),這個方法也可以使用總結
我們來回答一下開篇的5個問題
1:導航守衛(wèi)的執(zhí)行順序是怎么樣的?beforeRouteLeave < beforeEach < beforeRouteUpdate < beforeEnter < beforeRouteEnter < beforeResolve < afterEach
2:導航守衛(wèi)中的next的用處?next的作用,使導航守衛(wèi)隊列的繼續(xù)向下迭代
3:為什么afterEach守衛(wèi)沒有next?afterEach根本不在導航守衛(wèi)隊列內(nèi),沒有迭代的next
4:beforeEach是否可以疊加?beforeEach是可以疊加的,所有的全局前置守衛(wèi)按順序存放在beforeHooks的數(shù)組里面,
5:路由跳轉經(jīng)歷了哪幾部分?路由跳轉的核心方法是transitionTo,在跳轉過程中經(jīng)歷了一次confirmTransition,
(beforeRouteLeave < beforeEach < beforeRouteUpdate < beforeEnter < 異步組件加載)這樣順序的queue為第一個,
在第一個queue迭代完畢后,執(zhí)行第二個(beforeRouteEnter < beforeResolve)這樣順序的queue,
在執(zhí)行完畢后,開始執(zhí)行updateRoute,之后執(zhí)行全局的afterEach守衛(wèi)。最后完成路由的跳轉。
5個問題解答完畢,希望對你的業(yè)務有幫助。文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/98088.html
摘要:全局前置守衛(wèi)使用注冊一個全局前置守衛(wèi)。守衛(wèi)是異步解析執(zhí)行,此時導航在所有守衛(wèi)完之前一直處于等待中。中斷當前的導航。不過可以通過傳一個回調(diào)函數(shù)來訪問組建實例。注意是支持傳遞回調(diào)的唯一守衛(wèi)。用創(chuàng)建好的實例調(diào)用守衛(wèi)中傳給的回調(diào)函數(shù)。 全局前置守衛(wèi) 使用router.beforeEach注冊一個全局前置守衛(wèi)。 const router = new VueRouter({...}) router...
摘要:問有哪些鉤子使用場景的實現(xiàn)可以點這里前面我們用大白話講過什么是鉤子,這里在重復一下,就是在什么什么之前什么什么之后英文叫專業(yè)點叫生命周期,裝逼點可以叫守衛(wèi)中也存在鉤子的概念分為三步記憶全局守衛(wèi)路由獨享守衛(wèi)組件獨享守衛(wèi)全局守衛(wèi)很好理解,全 20190218問 Vue-router有哪些鉤子?使用場景? router的實現(xiàn)可以點這里 前面我們用大白話講過什么是鉤子,這里在重復一下,就是在...
摘要:調(diào)用全局的守衛(wèi)。在被激活的組件里調(diào)用。用創(chuàng)建好的實例調(diào)用守衛(wèi)中傳給的回調(diào)函數(shù)。 本文適用于對 Vue.js 和 vue-router 有一定程度了解的開發(fā)者除特殊說明,vue-router 版本為 3.0.2 正文 路由 class 匹配 路由匹配后會給該標簽添加 class 屬性值 .router-link-active,該功能在嵌套路由中十分方便 class 的實際屬性值可以通...
摘要:用存儲用戶路由守衛(wèi)路由中設置的字段就在當中每次跳轉的路徑登錄狀態(tài)下訪問頁面會跳到如果沒有訪問任何頁面。一個簡單的保存登錄狀態(tài)的小。 Vue項目中實現(xiàn)用戶登錄及token驗證 先說一下我的實現(xiàn)步驟: 使用easy-mock新建登錄接口,模擬用戶數(shù)據(jù) 使用axios請求登錄接口,匹配賬號和密碼 賬號密碼驗證后, 拿到token,將token存儲到sessionStorage中,并跳轉到首...
摘要:在失活的組件里調(diào)用離開守衛(wèi)。調(diào)用全局的守衛(wèi)。用創(chuàng)建好的實例調(diào)用守衛(wèi)中傳給的回調(diào)函數(shù)。路由元信息定義路由的時候可以配置字段 vue-router1 router-link 導航到不同組件 Go to Foo Go to Bar 定義路由const routes = [ { path: /foo, component: Foo }, { path: /bar, component: B...
閱讀 3792·2023-01-11 11:02
閱讀 4300·2023-01-11 11:02
閱讀 3121·2023-01-11 11:02
閱讀 5231·2023-01-11 11:02
閱讀 4793·2023-01-11 11:02
閱讀 5568·2023-01-11 11:02
閱讀 5371·2023-01-11 11:02
閱讀 4070·2023-01-11 11:02