# 4-4 路由守衛(Navigation Guards)
Vue Router 提供了 Navigation Guards (Vue Router 官方翻譯「導航守衛」,但是讀起來就覺得怪,本書以下統一採用原文 「Navigation Guards」) Hook 方法,作用類似於我們在 1-7 所介紹的生命週期函式,讓我們可以在特定的時機 (變更路由的前後) 去自動調用它們。
Navigation Guards 分別提供了 「全域」、「路由」、「元件」 這三種情況下不同的 Hook 供開發者們使用, 接下來我們就按照順序一一介紹這些 Navigation Guards 的使用方式。
# beforeEach
(全域)
我們可以直接在 router.beforeEach
註冊對應的 callback,這樣當「每一個」路由要進入之前,都會先經過這裡:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// do something...
});
2
3
4
5
6
我們可以在 router.beforeEach
的 callback 函式內取得 to
與 from
,它們分別代表:
to
: 即將進入的路由。from
: 從何處進入的路由。
我們可以在這個 callback 函式裡面執行任何動作,例如身份驗證等等。
router.beforeEach(async (to, from) => {
// canUserAccess() returns `true` or `false`
return await canUserAccess(to);
})
2
3
4
當 canUserAccess
的結果回傳 false
時,路由的切換將會被禁止,
而回傳值若為 true
或是 undefined
(預設情況),則表示路由可以正常執行。
而除了 to
與 from
之外,還有第三個參數 next
。
Vue Router 在過去的版本中, next()
用來表示繼續往下執行的 callback,如果沒有呼叫它,路由就會中斷。
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) {
next({ name: 'Login' });
}
else {
next();
}
})
2
3
4
5
6
7
8
但是自從 Vue Router 4 開始,前面的這段程式我們可以改成:
router.beforeEach((to) => {
if (to.name !== 'Login' && !isAuthenticated) {
return { name: 'Login' }
}
})
2
3
4
5
也會有一樣的效果。
注意
自 Vue Router 4 開始, next
已經不是必要選項了,而且在未來版本的 Vue Router 極有可能將它移除。
也就是說,如果 router.beforeEach
只要不是回傳 false
的情況下,路由的切換將會正常執行。
不必再像過去必須呼叫 next()
才能繼續。
# beforeResolve
(全域)
router.beforeResolve
的作用與前面的 router.beforeEach
類似,也是在路由跳轉前觸發,
但是時間點會晚於 router.beforeEach
以及非同步路由元件解析以後才被調用。
它的 callback 函式一樣有 to
、 from
以及 next
三個參數。
router.beforeResolve((to, from, next) => {
// do something...
});
2
3
4
# afterEach
(全域)
router.afterEach
與 router.beforeEach
相反,在路由跳轉結束後才會觸發。
它的 callback 函式只有 to
、 from
不會有 next
參數,因此不會影響路由的跳轉。
但是多了一個 failure
參數用來表示路由跳轉失敗。
所以我們可以使用 router.afterEach
來搭配像是 GA 追蹤一類的工具,來記錄使用者的瀏覽紀錄:
router.afterEach((to, from, failure) => {
if (!failure) {
sendToAnalytics(to.fullPath);
}
else {
// fallback...
}
});
2
3
4
5
6
7
8
這個 Hook 對於網站行為的分析來說是相當實用的功能。
# beforeEnter
(路由)
beforeEnter
不像前面幾組屬於全域型的 Hook,因此只能在 route
物件內註冊:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
2
3
4
5
6
7
8
9
10
而 beforeEnter
的作用、行為與前面介紹的 beforeEach
完全一樣,差別在於 beforeEnter
可以依照 routes
規則來選擇是否要註冊這個 Hook。
而 beforeEach
比較霸道一些,一旦註冊了,整個應用程式的路由都會進入這個 Hook 裡面。
# 元件內的 Navigation Guards Hooks
除了外部註冊的 Hook 之外, Navigation Guards 也提供了屬於單一元件內的 Hooks,分別是 beforeRouteEnter
、beforeRouteUpdate
、 beforeRouteLeave
這三種 Hooks。
使用方式跟我們前面介紹的生命週期 Hook 完全一樣:
<template> ... </template>
<script>
export default {
data () {
return { ... }
},
beforeRouteEnter (to, from) {
// ...
},
beforeRouteUpdate (to, from) {
// ...
},
beforeRouteLeave (to, from) {
// ...
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# beforeRouteEnter
beforeRouteEnter
Hook 在路由尚未進入該元件時被調用,與 beforeEach
、 beforeEnter
一樣,
提供了 to
, from
, next
三個參數。
要注意的是, beforeRouteEnter
Hook 與 beforeCreate
一樣,在裡面是拿不到 this
的,因為元件的實體還沒被建立。
不一樣的地方是,我們可以在 next()
取得元件的實體:
beforeRouteEnter (to, from, next) {
// 這裏沒有 this!!!
next(vm => {
// 可以透過 vm 指向元件實體
});
}
2
3
4
5
6
7
# beforeRouteUpdate
beforeRouteUpdate
Hook 會在當路由被改變,但是元件本身仍是同一個的時候被調用。
像是 URL 從 /users/1
跳轉到 /users/2
,雖然路由被更新了,但是對應的仍是同一個元件實體。
由於原本的元件並未被銷毀,而是更新內容,這個時候就會觸發 beforeRouteUpdate
Hook。
內部的參數與 beforeRouteEnter
大致相同,除了沒有 next()
可用。
因為此時已經可以透過 this
指向元件的實體物件了。
像是當元件初次載入的時候,我們可以透過 getPost()
取得 Ajax 的內容,然後存入 data
的 post
。
再來,假設路由從 /post/1
變成 /post/2
,此時我們就可以利用 beforeRouteUpdate
來取得新的資料,並更新 this.post
的內容:
// post.vue
export default {
data() {
return {
post: null,
error: null,
}
},
beforeRouteEnter(to, from, next) {
// 因元件未建立,只能透過 next 來取得實體
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
async beforeRouteUpdate(to, from) {
// 路由更新前
this.post = null;
try {
this.post = await getPost(to.params.id)
} catch (error) {
this.error = error.toString()
}
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# beforeRouteLeave
最後一個 beforeRouteLeave
Hook 則是當路由要離開這個元件時被自動調用,提供的參數一樣是 to
以及 from
。
beforeRouteLeave
通常會用在詢問使用者是否要跳轉到另一個路由時使用:
beforeRouteLeave (to, from) {
const answer = window.confirm('確定要離開嗎? 你還有更新尚未存檔!')
if (!answer) return false;
}
2
3
4
此時如果回傳 false
就可以阻擋使用者路由的跳轉。
# Navigation Guards 執行的順序
在這個小節裡,我們已經完整地介紹了 Vue Router 提供的 Navigation Guards,以及使用的時機。 在這個小節的最後,我們來釐清一下這些 Hook 執行的順序:
當我們切換路由時,依序會進行:
beforeRouteLeave
離開目前路由 (元件)beforeEach
開始進入新路由之前 (全域)beforeEnter
開始進入新路由之前 (路由)beforeRouteEnter
路由尚未進入該元件時 (元件)beforeResolve
路由與所搭配的元件已被解析 (全域)afterEach
當路由跳轉結束後 (全域)beforeCreate
元件實體建立前 (Vue Hook)created
元件實體已建立 (Vue Hook)beforeMount
元件實體掛載前 (Vue Hook)mounted
元件實體掛載完成 (Vue Hook)beforeRouteEnter
內的next()
回呼函式beforeRouteUpdate
當路由更新時 (僅限同屬一個元件的情況,也可能完全不會發生)
以上就是當我們進行路由跳轉時, Vue.js 以及 Vue Router 所提供的各種 Hook 以及它們觸發的順序。
小提醒
與生命週期的 Hooks 一樣, Vue 3.0 在引進 Composition API 搭配 setup
之後,與原本 Hooks 的使用方式也有些許不同,例如 beforeRouteLeave
變成 onBeforeRouteLeave
等等,這些變動與對應的使用方式,在本書第六章介紹 Composition API 時會有更詳細的解說。