想像你在做一個線上課程平台。學員可以先看課程列表,但購買後才能進入章節內容;管理員則有一個學員名單頁面。這些頁面都在同一個網站,卻需要不同的規則。
Day 23 我們學會了怎麼設定路由與讀取參數,今天要處理的是另一個問題:使用者切換頁面的時候,誰來決定能不能進去?還有,這個頁面的程式碼什麼時候才需要下載?
/courses/1/chapters,你覺得應該在哪一層檢查他有沒有購買?元件裡、路由裡,還是 API 裡?全域守衛 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(...) 把不常用的頁面拆開,首頁則保持靜態載入。