🧭 Vue Router:單頁應用路由管理
📌 什麼是 SPA(單頁應用)?
傳統網站 = 每次點連結,瀏覽器重新載入整個頁面 SPA = 只載入一次 HTML,之後「換頁」只是用 JavaScript 切換顯示的元件
傳統網站(Multi-Page Application):
點「關於」→ 瀏覽器送請求 → 伺服器回傳 about.html → 整頁重新載入
SPA(Single Page Application):
點「關於」→ JavaScript 攔截 → 切換顯示 About 元件 → 網址列更新
❌ 不重新載入頁面
✅ 使用者體驗更流暢
Vue Router 就是 Vue 官方提供的 SPA 路由管理工具。它不是瀏覽器原生功能,而是用 JavaScript 的 History API 封裝出來的。
📌 安裝與基本設定
# 用 npm 安裝(Vite 專案)
npm install vue-router@4
定義路由:router/index.js
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 引入頁面元件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import NotFound from '@/views/NotFound.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
// 動態路由:用冒號 :id 定義參數
{
path: '/user/:id',
name: 'UserProfile',
component: () => import('@/views/UserProfile.vue'), // 懶載入
props: true // 將路由參數作為 props 傳入
},
// 巢狀路由
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue'),
children: [
{ path: '', component: () => import('@/views/DashboardHome.vue') },
{ path: 'settings', component: () => import('@/views/DashboardSettings.vue') },
{ path: 'profile', component: () => import('@/views/DashboardProfile.vue') }
]
},
// 404 頁面(萬用路由,放最後)
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
const router = createRouter({
// createWebHistory 用的是瀏覽器的 History API
// 這是原生 JS 的 API,Vue Router 只是封裝它
history: createWebHistory(),
routes
})
export default router
掛載到 Vue App:main.js
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) // 安裝 Vue Router
app.mount('#app')
App.vue 中使用
<template>
<div id="app">
<nav>
<!-- router-link 取代 <a> 標籤 -->
<router-link to="/">首頁</router-link>
<router-link to="/about">關於</router-link>
<router-link :to="{ name: 'UserProfile', params: { id: 1 } }">
個人檔案
</router-link>
</nav>
<!-- router-view 是路由元件的顯示區域 -->
<router-view />
</div>
</template>
📌 動態路由與取得參數
<!-- views/UserProfile.vue -->
<template>
<div>
<h1>使用者 #{{ userId }}</h1>
<p>查詢參數 tab:{{ tab }}</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
// useRoute() 取得目前路由資訊
const route = useRoute()
// useRouter() 取得路由器實例(可以用來導航)
const router = useRouter()
// 路由參數 /user/:id → route.params.id
const userId = computed(() => route.params.id)
// 查詢參數 /user/1?tab=posts → route.query.tab
const tab = computed(() => route.query.tab || 'info')
// 程式化導航
function goHome() {
router.push('/')
}
function goToUser(id) {
router.push({ name: 'UserProfile', params: { id } })
}
function goBack() {
router.back() // 等同於瀏覽器上一頁
}
</script>
📌 導航守衛 (Navigation Guards)
導航守衛就像「門衛」,在路由切換前做檢查。
全域守衛
// router/index.js
router.beforeEach((to, from, next) => {
// to: 要去的路由
// from: 來自的路由
// next: 放行函式
const isLoggedIn = !!localStorage.getItem('token')
// 需要登入的頁面
if (to.meta.requiresAuth && !isLoggedIn) {
// 導向登入頁,並記住原本要去的頁面
next({ path: '/login', query: { redirect: to.fullPath } })
} else {
next() // 放行
}
})
// 路由定義加上 meta
const routes = [
{
path: '/dashboard',
component: Dashboard,
meta: { requiresAuth: true } // 需要登入
},
{
path: '/login',
component: Login,
meta: { requiresAuth: false }
}
]
元件內守衛
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
// 離開頁面前確認
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('有未儲存的變更,確定離開嗎?')
if (!answer) return false // 取消離開
}
})
</script>
📌 路由懶載入 (Lazy Loading)
// ❌ 全部打包在一起(首次載入慢)
import Home from './views/Home.vue'
import About from './views/About.vue'
import Dashboard from './views/Dashboard.vue'
// ✅ 懶載入:只在需要時才載入(動態 import 是原生 JS 語法!)
const routes = [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
{
path: '/dashboard',
// 還可以自訂 chunk 名稱
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue')
}
]
路由載入動畫
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<Suspense>
<component :is="Component" />
<template #fallback>
<div class="loading">載入中...</div>
</template>
</Suspense>
</transition>
</router-view>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
📌 完整範例:多頁面導航
<!-- App.vue -->
<template>
<div>
<nav class="navbar">
<router-link
v-for="link in navLinks"
:key="link.path"
:to="link.path"
active-class="active"
>
{{ link.icon }} {{ link.label }}
</router-link>
</nav>
<main>
<router-view />
</main>
</div>
</template>
<script setup>
const navLinks = [
{ path: '/', label: '首頁', icon: '🏠' },
{ path: '/courses', label: '課程', icon: '📚' },
{ path: '/dashboard', label: '儀表板', icon: '📊' },
{ path: '/about', label: '關於', icon: 'ℹ️' }
]
</script>
💡 小提醒
router-link比<a>好,因為不會重新載入頁面- 動態路由參數變化時,元件不會重新建立——用
watch監聽route.params - 路由懶載入用的
import()是 ES Module 原生語法(這個真的是 JS 的!) useRoute和useRouter是 Vue Router 的 API,不是 JS 原生的