iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Modern Web

重新認識Vue 走過路過不要錯過系列 第 24 篇

Vue 走過路過不要錯過 Day24 - Vue Router 進階實作,導航守衛與路由懶載入

  • 分享至 

  • xImage
  •  

前言

想像你在做一個線上課程平台。學員可以先看課程列表,但購買後才能進入章節內容;管理員則有一個學員名單頁面。這些頁面都在同一個網站,卻需要不同的規則。

Day 23 我們學會了怎麼設定路由與讀取參數,今天要處理的是另一個問題:使用者切換頁面的時候,誰來決定能不能進去?還有,這個頁面的程式碼什麼時候才需要下載?

先想一想

  1. 使用者直接在網址列輸入 /courses/1/chapters,你覺得應該在哪一層檢查他有沒有購買?元件裡、路由裡,還是 API 裡?
  2. 如果網站有 50 個頁面,使用者只進首頁,所有頁面的 JavaScript 都要一次下載嗎?

實作一:用全域守衛擋住未登入的使用者

全域守衛 router.beforeEach 會在每次路由切換前執行。我們在需要登入的路由上加一個 meta 標記:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import CourseList from '../views/CourseList.vue'
import Chapters from '../views/Chapters.vue'
import Login from '../views/Login.vue'
import { isLoggedIn } from '../stores/auth'
 
const routes = [
  { path: '/', component: CourseList },
  { path: '/login', component: Login },
  {
    path: '/courses/:id/chapters',
    component: Chapters,
    meta: { requiresAuth: true },
  },
]
 
const router = createRouter({
  history: createWebHistory(),
  routes,
})
 
router.beforeEach((to) => {
  // 目標頁面需要登入,而且目前沒有登入
  if (to.meta.requiresAuth && !isLoggedIn.value) {
    return { path: '/login' }
  }
  // 沒有回傳值(或回傳 true)代表放行
})
 
export default router

這裡的登入狀態用一個簡單的 ref 模擬:

// src/stores/auth.js
import { ref } from 'vue'
 
export const isLoggedIn = ref(false)
 
export function login() {
  isLoggedIn.value = true
}

守衛的回傳值決定結果:

  • 不回傳或回傳 true:放行。
  • 回傳一個路由位置(例如 { path: '/login' }):導向那個頁面。
    常見的陷阱有兩個。第一,如果寫成 if 判斷卻忘了在導向時 return,程式會繼續執行,有可能造成無限跳轉。第二,這個守衛只存在於前端,使用者可以直接修改前端程式碼繞過它,所以真正的權限檢查仍要在後端的 API 完成。

實作二:用路由獨享守衛處理單一頁面

如果某個規則只針對一個路由,可以直接寫在路由設定裡的 beforeEnter。例如學員名單只能給管理員看:

// src/router/index.js 的 routes 片段
import Students from '../views/Students.vue'
import { currentUser } from '../stores/auth'
 
const routes = [
  // ...其他路由
  {
    path: '/admin/students',
    component: Students,
    beforeEnter: () => {
      if (currentUser.value?.role !== 'admin') {
        return { path: '/' }
      }
    },
  },
]

beforeEnter 只在進入這條路由時觸發。如果同一個路由的參數變化(例如 /courses/1 切到 /courses/2),它不會再執行一次。規則單純時,用它比全域守衛更容易閱讀。

實作三:用元件內守衛防止遺失資料

假設章節頁面有一個筆記輸入框。使用者寫了一半想離開,我們希望跳出確認。這種情況適合用 onBeforeRouteLeave,它寫在元件裡,只在離開這個元件時觸發:

// Chapters.vue 的 script setup 部分
import { ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
 
const note = ref('')
const saved = ref(false)
 
onBeforeRouteLeave(() => {
  // 有內容但還沒儲存,詢問使用者
  if (note.value && !saved.value) {
    const ok = window.confirm('筆記尚未儲存,確定要離開嗎?')
    if (!ok) return false // 回傳 false 會取消這次導覽
  }
})

這裡要注意執行時機:onBeforeRouteLeave 是在元件層級,只有離開這個元件才會觸發;全域的 beforeEach 則是每次切換都會觸發。兩者的差別,寫的時候要分清楚。

實作四:路由懶載入

先看靜態 import 的寫法:

import Students from '../views/Students.vue'

這行會讓 Students.vue 被打包進主要的 JavaScript 檔案,使用者一打開網站就會下載。改成動態 import() 的寫法:

const routes = [
  { path: '/', component: CourseList },
  {
    path: '/admin/students',
    component: () => import('../views/Students.vue'),
  },
]

() => import(...) 會回傳一個 Promise,Vue Router 會在進入這個路由時才去下載對應的檔案。

用 Vite 打包後,執行 npm run build,你會看到 dist/assets 裡多出幾個 chunk 檔案,主檔案的體積也變小。要確認它真的是在進入頁面時才下載,可以打開瀏覽器的 DevTools,切到 Network 面板,清空紀錄後從首頁點進學員名單,就會看到一個新的 JavaScript 檔案被請求。

適合懶載入的頁面通常是後台、報表這類不是每個人都會用到的頁面。首頁如果一打開就會用到的元件,就不必懶載入,否則反而會多一次請求,讓首頁變慢。

實作五:把守衛與懶載入組合起來

實際的專案通常會同時有公開頁面、需要登入的頁面,以及需要管理員權限的頁面。下面是一個完整的路由設定:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { isLoggedIn, currentUser } from '../stores/auth'
 
const routes = [
  // 公開頁面:直接載入,因為首頁每次都會用到
  { path: '/', component: () => import('../views/CourseList.vue') },
  { path: '/login', component: () => import('../views/Login.vue') },
 
  // 需要登入的頁面:懶載入,登入後才下載
  {
    path: '/courses/:id/chapters',
    component: () => import('../views/Chapters.vue'),
    meta: { requiresAuth: true },
  },
 
  // 需要管理員權限的頁面
  {
    path: '/admin/students',
    component: () => import('../views/Students.vue'),
    meta: { requiresAuth: true, requiresAdmin: true },
  },
]
 
const router = createRouter({
  history: createWebHistory(),
  routes,
})
 
router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isLoggedIn.value) {
    return { path: '/login' }
  }
  if (to.meta.requiresAdmin && currentUser.value?.role !== 'admin') {
    return { path: '/' }
  }
})
 
export default router

這樣設定後,使用者沒登入時,連需要權限的頁面的 JavaScript 都不會下載,只會被導向登入頁。

小結

  • 守衛決定能不能進入頁面:全域守衛適合通用規則,beforeEnter 適合單一路由,onBeforeRouteLeave 適合離開前的確認。
  • 懶載入決定什麼時候下載頁面:用 () => import(...) 把不常用的頁面拆開,首頁則保持靜態載入。
  • 前端守衛只改善使用體驗,真正的權限檢查仍必須在後端完成。

上一篇
Vue 走過路過不要錯過 Day 23 - Vue Router 基礎,路由設定、動態參數與查詢參數
系列文
重新認識Vue 走過路過不要錯過 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言