聊完了基礎的路由功能後,我們接著來看看關於路由的進階應用方式。你放心,前陣子一直提到的動態元件載入,這邊一樣會出現,所以先給各位打個預防針。不過,如果你真的看膩了什麼動態載入的事情,那麼你想要跳過我也是欣然接受。

何況,進來就進來,出去就出去, 哪有人一直動態載入的啦。


路由守衛方法( Guards Methods

Navigation Guards

在 Router 當中提供了幾種所謂的路由守衛方法:

其實我覺得叫路由防衛比較好(聽起來超中二的

  • beforeEach 每個路由要被執行之前,都會先經過這裡,他會傳入三個參數:
    • to 你要去的路由位置。
    • from 你從哪一個路由位置進來,如果沒有,預設是 null
    • next() 繼續往下執行的回呼函式,你必須要呼叫他才能繼續執行。
  • beforeResolve 當路由與所搭配的元件都被解析後,會執行這個函式,傳入參數跟 beforeEach 一樣,功能也一樣。
  • beforeEnter 用於 Route 設定的地方,傳入參數跟 beforeEach 一樣,功能也一樣。

首先我們說明一下 next() 這個回呼函式,基本上無論你在哪一個階段,只要 next() 沒有被呼叫到,那麼你的路由基本上就會被中斷。而這個 next() 可以接受的呼叫方法有:

  • next() 就這樣,很單純的呼叫。
  • next(false) 代表中斷這個路由執行,畫面就會停住了。
  • next({ name: 'HelloKitty' }) 代表前往路由名稱為 HelloKitty 的地方。這種呼叫方式可以套用 <router-link>to 的屬性,可以參考我昨天的文章 Router 基本入門 Day 9
  • next(Error) 傳入一個 Error 的實例,該路由會被中斷,然後發一個事件給 router.onError()

至於剛剛說的 beforeEnter 的使用方式,是在 Route 設定的時候:

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/helloworld',
      component: HelloWorld,
      beforeEnter (to, from, next) {
        next();
      }
    }
  ]
});

然後在元件裡面,當你使用了 Vue Router 的時候,在整個生命週期當中,也提供了三組方法可以使用:

  • beforeRouteEnter 路由尚未進入該元件時會執行,一樣會三個傳入三個參數:
    • to 你要去的路由位置。
    • from 你從哪一個路由位置進來,如果沒有,預設是 null
    • next() 繼續往下執行的回呼函式,你必須要呼叫他才能繼續執行。
  • beforeRouteUpdate 當此元件被重複使用時,路由有更新,會呼叫這個方法,參數跟 beforeRouteEnter 相同。
  • beforeRouteLeave 當路由要離開該元件時,會呼叫這個方法,參數跟 beforeRouteEnter 相同。

以上三組沒有太大的區別,唯一有差異的是 beforeRouteEnter,由於他是在路由進入點之前,也就是說在路由尚未進入,所以他沒有 this 可以用,而其他兩個,在方法中的 this 均指向元件實體。

beforeRouteEnter 若要拿到元件實體,必須使用 next() 來實作:

export default {
  name: 'HelloWorld',
  beforeRouteEnter (to, from, next) {
    next(vm => {
      // 這裡的 vm 就是 HelloWorld 這個元件的實體。
    })
  }
}

或許你會問說,這些東西到底有什麼好處?為何要叫做 路由守衛 ?我們底下舉出一些例子,你思考一下當你要做這些事情的時候,你會怎麼做?

  1. 你的路徑是需要檢查登入的時候。
  2. 你必須要檢查相對應資料是否存在時。
  3. 你在頁面開始之前需要做非同步操作。
  4. 你想讓特定來源走向不同路由。
  5. 你想在每個頁面都加上蓋版廣告。

我們以登入檢查為例子,如果你沒有這些方法,那麼你勢必要在每個路徑所經過的地方,都要綁上一個檢查的步驟。這樣其實在後續維護上就會變得相當棘手。而,beforeEachbeforeResolvebeforeEnter 或是 beforeRouteEnter 就可以幫助你做掉這件事情。

詳細的流程可以參考官方說明:

The Full Navigation Resolution Flow

當然,你可能不會想放在 beforeRouteEnter,這樣等於每個元件都要做。所以,我們以 beforeEnter 為例子,就可以這麼做:

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/need-to-login',
      component: HelloWorld,
      beforeEnter (to, from, next) {
        checkLoginStatus()
          .then(next)
          .catch(() => {
            next({ name: 'signin' })
          });
      }
    }
  ]
});

倘若你的 need-to-login 底下還有 children 的話,也不用擔心,因為他一定會執行。即便你是直接進入其下的路由設定,他的上游的動作還是會被執行的。

不過,請注意一點,由於路由執行的順序,會先走過一次 beforeRouteLeave,所以,如果你在這邊有做什麼 登出 的動作,那麼你可能每次登入,換頁後就會一直被彈到登入頁面。

另外一個方式,是可以放在最外層的 beforeEach 的地方,作法大同小異,但為了識別 Route,所以你可以使用 meta 來儲存一些識別用資料,舉例來說:

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/need-to-login',
      component: HelloWorld,
      meta: {
        needLogin: true
      }
    }
  ]
});

router.beforeEach(function(to, from, next) {
  if (to.meta.needLogin === true) {
    return checkLoginStatus()
      .then(next)
      .catch(() => {
        next({ name: 'signin' })
      });
  }
  next();
});

但是!要注意一點,由於 beforeEach 並不會幫你做麼上游的動作,他是 每一個 路由進入之前會做一些事情,所以當你的 need-to-login 其下有子路由,那麼也一定要加上相同的 meta 來儲存識別資料,否則,你就會跑出一個漏洞了。


動態載入又來了

既然,這個方法可以 阻擋 路由實例的流程,那麼我們在阻擋的同時,是不是可以拿來做其他的事情?當然,可以,不然我就不會又跳出來講動態載入了。

如果聽膩的人左轉沒關係,謝謝。

這裡我有寫一點邪魔歪道 心臟比較大顆的可以試試看。

是的,就如同之前提到的 import,Vue Router 本身也支援懶加載這件事情,所以,我們在載入元件的時候,可以這麼做:

const HelloWorld = () => import('@/components/HelloWorld.vue')

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/helloworld',
      component: HelloWorld,
      beforeEnter (to, from, next) {
        next();
      }
    }
  ]
});

你在開發模式其實不會有感覺,當你運行 yarn build 將正式環境打包後,你會發現你使用 import 的一些模組,會被分割成不同的 .js 檔案。而這些檔案會在 需要的時候 才會被載入到前端,用以實現懶加載這件事情。

當然,這也是相當好用的方法。不過,今天如果我連 元件本身 都還不確定存不存在呢?

請回想一下 Component 魔術方法 Day 5

還有 Component 魔術方法 Day 6

所以,我們可以這麼做:

  • 利用 XHR 把檔案讀取進來。
  • 利用 Promise 把元件餵給 component
  • 利用 Vue.options 來配發 component
  • 利用 JavaScript 特性來更改 component
  • 利用剛剛的邪魔歪道餵入 Functional Component。

有沒有發現,其實到最後都是一種 邪魔歪道 的風格。

沒辦法,我長得不正。

我這邊不會在提 XHR, Promise 與 Vue.options 怎麼做了,我們就來講一個,利用 JavaScript 語言本身的特(缺)性(陷),來更改 components。我這邊先上 Code 與執行結果,但是不要問我為什麼會這樣!

import Router from 'vue-router';
import HelloKitty from '@/components/HelloKitty.vue';

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/helloworld',
      component: null,
      beforeEnter (to, from, next) {
        to.matched[0].components.default = HelloKitty;
        next();
      }
    }
  ]
});

關於為何會這樣,可以參考 Kuro 講解 JavaScript 的文章。

JavaScript 是「傳值」或「傳址」

說句實在話,你是不是覺得 JavaScript 好棒棒!


小結

我這邊就大幅縮減動態載入的事情跟範例,反正我們之後還會繼續提到(啊哈哈哈哈)。關於路由的使用大概是這樣,如果你沒有要做什麼奇怪的事情,那麼基本上不太會用到更深的東西了。

官方文件 還是要參考一下!

ITHome 鐵人賽同步刊登 Router 進階應用 Day 10