# 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...

});
1
2
3
4
5
6

我們可以在 router.beforeEach 的 callback 函式內取得 tofrom,它們分別代表:

  • to: 即將進入的路由。
  • from: 從何處進入的路由。

我們可以在這個 callback 函式裡面執行任何動作,例如身份驗證等等。

router.beforeEach(async (to, from) => {
  // canUserAccess() returns `true` or `false`
  return await canUserAccess(to);
})
1
2
3
4

canUserAccess 的結果回傳 false 時,路由的切換將會被禁止, 而回傳值若為 true 或是 undefined (預設情況),則表示路由可以正常執行。

而除了 tofrom 之外,還有第三個參數 next

Vue Router 在過去的版本中, next() 用來表示繼續往下執行的 callback,如果沒有呼叫它,路由就會中斷。

router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) {
    next({ name: 'Login' });
  }
  else {
    next();
  }
})
1
2
3
4
5
6
7
8

但是自從 Vue Router 4 開始,前面的這段程式我們可以改成:

router.beforeEach((to) => {
  if (to.name !== 'Login' && !isAuthenticated) {
    return { name: 'Login' }
  }
})
1
2
3
4
5

也會有一樣的效果。

注意

自 Vue Router 4 開始, next 已經不是必要選項了,而且在未來版本的 Vue Router 極有可能將它移除。 也就是說,如果 router.beforeEach 只要不是回傳 false 的情況下,路由的切換將會正常執行。

不必再像過去必須呼叫 next() 才能繼續。

# beforeResolve (全域)

router.beforeResolve 的作用與前面的 router.beforeEach 類似,也是在路由跳轉前觸發, 但是時間點會晚於 router.beforeEach 以及非同步路由元件解析以後才被調用。

它的 callback 函式一樣有 tofrom 以及 next 三個參數。

router.beforeResolve((to, from, next) => {
  // do something...

});
1
2
3
4

# afterEach (全域)

router.afterEachrouter.beforeEach 相反,在路由跳轉結束後才會觸發。 它的 callback 函式只有 tofrom 不會有 next 參數,因此不會影響路由的跳轉。

但是多了一個 failure 參數用來表示路由跳轉失敗。

所以我們可以使用 router.afterEach 來搭配像是 GA 追蹤一類的工具,來記錄使用者的瀏覽紀錄:

router.afterEach((to, from, failure) => {
  if (!failure) {
    sendToAnalytics(to.fullPath);
  }
  else {
    // fallback...
  }
});
1
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
    },
  },
]
1
2
3
4
5
6
7
8
9
10

beforeEnter 的作用、行為與前面介紹的 beforeEach 完全一樣,差別在於 beforeEnter 可以依照 routes 規則來選擇是否要註冊這個 Hook。

beforeEach 比較霸道一些,一旦註冊了,整個應用程式的路由都會進入這個 Hook 裡面。

# 元件內的 Navigation Guards Hooks

除了外部註冊的 Hook 之外, Navigation Guards 也提供了屬於單一元件內的 Hooks,分別是 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave 這三種 Hooks。

使用方式跟我們前面介紹的生命週期 Hook 完全一樣:

<template> ... </template>

<script>
export default {
  data () {
    return { ... }
  },
  beforeRouteEnter (to, from) {
    // ...
  },
  beforeRouteUpdate (to, from) {
    // ...
  },
  beforeRouteLeave (to, from) {
    // ...
  },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# beforeRouteEnter

beforeRouteEnter Hook 在路由尚未進入該元件時被調用,與 beforeEachbeforeEnter 一樣, 提供了 to, from, next 三個參數。

要注意的是, beforeRouteEnter Hook 與 beforeCreate 一樣,在裡面是拿不到 this 的,因為元件的實體還沒被建立。

不一樣的地方是,我們可以在 next() 取得元件的實體:

beforeRouteEnter (to, from, next) {
  // 這裏沒有 this!!!

  next(vm => {
    // 可以透過 vm 指向元件實體
  });
}
1
2
3
4
5
6
7

# beforeRouteUpdate

beforeRouteUpdate Hook 會在當路由被改變,但是元件本身仍是同一個的時候被調用。 像是 URL 從 /users/1 跳轉到 /users/2,雖然路由被更新了,但是對應的仍是同一個元件實體。

由於原本的元件並未被銷毀,而是更新內容,這個時候就會觸發 beforeRouteUpdate Hook。 內部的參數與 beforeRouteEnter 大致相同,除了沒有 next() 可用。

因為此時已經可以透過 this 指向元件的實體物件了。

像是當元件初次載入的時候,我們可以透過 getPost() 取得 Ajax 的內容,然後存入 datapost。 再來,假設路由從 /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()
    }
  },
}
1
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;
}
1
2
3
4

此時如果回傳 false 就可以阻擋使用者路由的跳轉。

在這個小節裡,我們已經完整地介紹了 Vue Router 提供的 Navigation Guards,以及使用的時機。 在這個小節的最後,我們來釐清一下這些 Hook 執行的順序:

當我們切換路由時,依序會進行:

  1. beforeRouteLeave 離開目前路由 (元件)
  2. beforeEach 開始進入新路由之前 (全域)
  3. beforeEnter 開始進入新路由之前 (路由)
  4. beforeRouteEnter 路由尚未進入該元件時 (元件)
  5. beforeResolve 路由與所搭配的元件已被解析 (全域)
  6. afterEach 當路由跳轉結束後 (全域)
  7. beforeCreate 元件實體建立前 (Vue Hook)
  8. created 元件實體已建立 (Vue Hook)
  9. beforeMount 元件實體掛載前 (Vue Hook)
  10. mounted 元件實體掛載完成 (Vue Hook)
  11. beforeRouteEnter 內的 next() 回呼函式
  12. beforeRouteUpdate 當路由更新時 (僅限同屬一個元件的情況,也可能完全不會發生)

以上就是當我們進行路由跳轉時, Vue.js 以及 Vue Router 所提供的各種 Hook 以及它們觸發的順序。

小提醒

與生命週期的 Hooks 一樣, Vue 3.0 在引進 Composition API 搭配 setup 之後,與原本 Hooks 的使用方式也有些許不同,例如 beforeRouteLeave 變成 onBeforeRouteLeave 等等,這些變動與對應的使用方式,在本書第六章介紹 Composition API 時會有更詳細的解說。

Last Updated: 1/8/2021, 4:24:16 PM