iT邦幫忙

2022 iThome 鐵人賽

DAY 23
2
Modern Web

真的好想離開 Vue 3 新手村 feat. CompositionAPI系列 第 23

Day 23: 來發 API 吧!Lifecycle Hooks and Navigation Guards 你要哪一個?

  • 分享至 

  • xImage
  •  

Outline

  • Lifecycle Hooks 快速總覽
  • Navigation guards 快速總覽
  • 發 API 挑哪個?

今天會著重介紹觸發時機,可以在什麼情境下使用,不會講解詳細的語法。

Lifecycle Hooks

1. setup

這是大家最熟悉的函式,在 Composition API 中,會裡面定義要暴露給模板的屬性跟方法。

setup() 是 Vue 元件生命週期中,第一個被呼叫的函式,在準備掛載元件前,會先執行 setup(),過程中會建立元件實例,完成內部屬性、狀態初始化。

我們會在 setup 內去同步註冊 Lifecycle Hooks。

2. Mount 階段

先定義完成 mounted 的意思,官方文件內的說明是:

  1. 該元件的所有同步子元件,也都完成 mount(ed)
  2. 元件的 DOM tree 建立並插入父容器中

簡單來說,假設根節點(通常是 App)已經在 DOM 上,就是指元件和他的子元件都掛到 DOM tree 上,稱為完成 mount(ed),通常會翻譯成「掛載」或「渲染」。

  • onBeforeMount:元件準備被掛載/渲染之前
  • onMounted:元件掛載完成後

3. Update 階段

什麼時候會更新?
通常是因為應響應式狀態改變,需要更新 DOM 內容。

  • onBeforeUpdate:觀察到響應式狀態變化,準備更新 DOM 之前
  • onUpdated:完成 DOM 更新後

4. Unmount 階段

先定義完成 unmounted 的意思,官方文件內的說明是:

  1. 元件的所有子元件都被 unmounted
  2. 元件的相關的響應式 (例如:setup() 期間初始化的 computed 和 watcher)

簡單來說,假設根節點(通常是 App)已經在 DOM 上,就是指元件和他的子元件都掛從 DOM tree 上移除,稱為完成 unmount(ed),通常會翻譯成「卸載」。

  • onBeforeUnmount:在元件要被卸載之前
  • onUnmounted:在元件要被卸載之後

Navigation guards

顧名思義,是用來守衛(guard)導航(Navigation),在每次導航前後呼叫,可以傳入非同步函式,並在內部使用await,會等到執行完成,才會繼續跳轉。

每個 Guard 都可以接到 (to, form) 兩個參數,分別代表目標路由位置當前路由位置,從 tofrom 物件中,可以取得導向的 hrefhashqueryparamsmeta 等等,可以透過這些參數,去 fetch 需要的資料。(完整屬性參考)

  1. Global Before Guards:router.beforeEach

    • 針對所有的 route
    • 每次 url 改變,第一個觸發的 guard
  2. Global Resolve Guards:router.beforeResolve

    • 針對所有的 route
    • 每次 url 改變,等到非同步的 guard 都執行完成後才會觸發,會是進入新路由前,最後一個觸發的 guard
  3. Global After Hooks:router.afterEach

    • 針對所有的 route
    • 進入新路由之後,第一個觸發的 guard
  4. Per-Route Guard:beforeEnter

    • 針對個別的 route 做設定,定義在 router.routes
    • 只有改變 route 時會觸發,paramsqueryhash 改變時不會觸發
  5. In-Component Guards(Composition API)
    可以用在由 <router-view> 渲染的元件 setup() 內。

    • onBeforeRouteUpdate:在路由更新前
    • onBeforeRouteLeave:在離開路由之前

從 Vue 建立的初始專案看順序

用類似 Vue 專案剛建立時的架構來看 Lifecycle Hook 和 Router Guard 被呼叫的順序。

進入網站後

  1. App - setup > onBeforeMount > onMounted
  2. 全域 guard - beforeEach
  3. /Home 路由 - beforeEnter
  4. 全域 guard - beforeResolve
  5. 全域 guard - afterEach

在載入路由頁面元件(View)時,會先呼叫 route guard 確定導向,才會開始跑 <router-view> 內元件的生命週期

  1. router-view(HomeView)- setup > onBeforeMount > onMounted

/ 切換到 /Rooms

  1. router-view(HomeView)- onBeforeRouteLeave
  2. 全域 guard - beforeEach
  3. /Rooms 路由 - beforeEnter
  4. 全域 guard - beforeResolve
  5. 全域 guard - afterEach
  6. router-view(HomeView)- onBeforeUnmount
  7. router-view(RoomsView)- setup > onBeforeMount
  8. router-view(HomeView)- onUnmounted
  9. router-view(RoomsView)- onMounted

發 API 挑哪個?

在元件內 fetch 資料,更新響應式資料的預設空值

  • onBeforeMount
  • onMounted
  • watchEffect
    • 雖然 watchEffect 不是生命週期的 Hook,但也很適合用在這種情境上

使用方式可以參考 - Day 21: 來發 API 吧!Async Composition API setup()


在進入路由頁面前先 GET 資料

有了 Route Guard,我們就可以在頁面(元件)載入之前,提前送出請求,避免等待資料的空白時間,也就不需要特別處理元件的 loading 狀態!
Vue Router 有 3 個 route guard 符合這個需求。

針對整個 url 的改變 針對 Route 個別設定
beforeEach、 beforeResolve beforeEnter

如果是針對整個網站的網址,在觸發導向時都要發 API,就適合使用 beforeEachbeforeResolve,舉例來說:要確認使用者是否登入,或是身份權限是否足夠,才能讓使用者看到網站畫面,就可以用這兩個 guard。

router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

如果是在特定的 route 頁面/元件,才需要發 API,就適合使用 beforeEnter,還可以透過帶 url 參數,去 get 特定的資料。

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      name: "home",
      component: HomeView,
    },
    {
      path: "/rooms",
      name: "rooms",
      component: () => import("../views/RoomsView.vue"),
      //進入 /rooms 前,先去 GET 全部房間資訊
      beforeEnter: async (to) => {
        //可是 route.js 內沒有 rooms 這個變數
        //這個方法建議配合狀態管理器使用
        rooms.value = await hotelAPI.GET("/room", to.params.id);
      },
    },
    {
      path: "/room/:id",
      name: "room",
      component: () => import("../views/RoomView.vue"),
      //可以透過 URL 參數,先去 GET 指定房間資訊
      beforeEnter: async (to) => {
        const id = parseInt(to.params.id);
        //可是 route.js 內沒有 roomInfo 這個變數
        //這個方法建議配合狀態管理器使用
        roomInfo.value = await hotelAPI.GET("/room", to.params.id);
      },
    },
  ],
});

註:當然也可以在 beforeEach guard 偵測這件事,那需要多寫 if (to.path === "/rooms"),當需要提前拿資料的 route 越來越多, beforeEach guard 內的邏輯就會變得更複雜。

但是!

這時候會遇到一個問題,route.js 內沒有辦法取到元件的變數,元件也不能直接取到 route.js 裡拿到的 room,這時候就可以搭配狀態管理器(例如:Pinia 和 Vuex),透過狀態管理器,可以將共用狀態抽出來,方便元件之間共用,要取用或更新共用狀態時,就可以直接從狀態管理器的 store 裡面拿,這也是狀態管理器的好用之處

沒用過狀態管理器的人,可能會覺得陌生詞彙很多,等到「狀態管理器」的章節會再提到。


在頁面參數切換時 GET 資料

在同個路由下做參數切換時,可以在該路由頁面/元件的 setup 內註冊 onBeforeRouteUpdate guard,每次參數切換時,自動從遠端拿到新資料。

舉例來說:url 從 /room/${id1}/room/${id2}
實際情境說明:

  1. 使用者直接更改手動輸入 url
  2. 底下有其他房間連結,點擊後切換路由參數
//inside <script setup>
import { onBeforeRouteUpdate } from "vue-router";
import { ref } from "vue";

const room = ref();

onBeforeRouteUpdate(async (to, from) => {
  if (to.params.id !== from.params.id) {
    room.value = await hotelAPI.GET("/room", to.params.id);
  }
});

注意上面這個範例,只有從 /room/${id1}/room/${id2} 會觸發 onBeforeRouteUpdate,從 /rooms/room/${id1} 並不會觸發 guard。


murmur

連假是鐵人賽殺手吧QQ
這篇文章寫的比較倉促,之後會再回頭好好整理一次
如果有什麼建議或使用經驗,歡迎留言跟我分享~

參考資料


上一篇
Day 22: Composition API async setup() + await 的限制
下一篇
Day 24: Before Pinia - 什麼是狀態(state)?為什麼需要狀態管理器?
系列文
真的好想離開 Vue 3 新手村 feat. CompositionAPI31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言