Vue Router 路由
Vue Router 是 Vue.js 官方的路由管理器,用於建立單頁應用程式(SPA)。它讓你可以將不同的 URL 對應到不同的元件,實現頁面之間的切換而不需要重新載入整個頁面。
安裝
npm install vue-router@4
基本設定
建立路由
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
註冊路由
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
使用 RouterView 和 RouterLink
<!-- App.vue -->
<template>
<nav>
<RouterLink to="/">首頁</RouterLink>
<RouterLink to="/about">關於</RouterLink>
</nav>
<!-- 路由匹配的元件會渲染在這裡 -->
<RouterView />
</template>
路由模式
Hash 模式
URL 會包含 #,如 http://example.com/#/about:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes
})
History 模式(推薦)
URL 看起來像正常的 URL,如 http://example.com/about:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes
})
使用 History 模式需要伺服器配置,確保所有路由都返回
index.html。動態路由
路由參數
const routes = [
{
path: '/user/:id',
name: 'user',
component: User
}
]
在元件中存取參數:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
</script>
<template>
<p>使用者 ID: {{ $route.params.id }}</p>
</template>
多個參數
const routes = [
{
path: '/user/:userId/post/:postId',
component: UserPost
}
]
可選參數
const routes = [
{
// id 是可選的
path: '/user/:id?',
component: User
}
]
響應參數變化
當路由參數變化時(如 /user/1 到 /user/2),元件實例會被重用。使用 watch 來響應變化:
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
(newId) => {
// 獲取新的使用者資料
fetchUser(newId)
}
)
</script>
巢狀路由
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 預設子路由
path: '',
component: UserHome
},
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
]
<!-- User.vue -->
<template>
<div class="user">
<h1>使用者 {{ $route.params.id }}</h1>
<!-- 子路由會渲染在這裡 -->
<RouterView />
</div>
</template>
程式化導航
使用 router.push
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function goToUser(id) {
// 字串路徑
router.push('/user/' + id)
// 帶路徑的物件
router.push({ path: '/user/' + id })
// 命名路由
router.push({ name: 'user', params: { id } })
// 帶 query
router.push({ path: '/search', query: { q: 'vue' } })
}
</script>
其他導航方法
// 替換當前歷史記錄(不會在 history 中留下記錄)
router.replace({ path: '/about' })
// 前進/後退
router.go(1) // 前進一步
router.go(-1) // 後退一步
router.back() // 同 router.go(-1)
router.forward() // 同 router.go(1)
RouterLink 進階用法
<template>
<!-- 命名路由 -->
<RouterLink :to="{ name: 'user', params: { id: 123 }}">
使用者
</RouterLink>
<!-- 帶 query -->
<RouterLink :to="{ path: '/search', query: { q: 'vue' }}">
搜尋
</RouterLink>
<!-- 替換歷史記錄 -->
<RouterLink to="/about" replace>
關於
</RouterLink>
<!-- 自訂 active class -->
<RouterLink to="/about" active-class="my-active">
關於
</RouterLink>
</template>
路由守衛
全域守衛
// router/index.js
router.beforeEach((to, from) => {
// to: 即將進入的路由
// from: 當前路由
// 檢查是否需要登入
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'login' }
}
// 返回 false 取消導航
// return false
// 不返回或返回 true 表示允許導航
})
router.afterEach((to, from) => {
// 導航完成後執行
document.title = to.meta.title || '預設標題'
})
路由獨享守衛
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
if (!isAdmin()) {
return { name: 'home' }
}
}
}
]
元件內守衛
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 離開前確認
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = confirm('有未儲存的變更,確定要離開嗎?')
if (!answer) return false
}
})
// 路由更新時(如參數變化)
onBeforeRouteUpdate((to, from) => {
// 例如 /user/1 -> /user/2
fetchUser(to.params.id)
})
</script>
路由 Meta
const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: '管理後台',
roles: ['admin']
}
}
]
// 在守衛中使用
router.beforeEach((to, from) => {
if (to.meta.requiresAuth) {
// 檢查登入狀態
}
})
路由懶載入
使用動態 import 實現程式碼分割:
const routes = [
{
path: '/',
component: () => import('../views/Home.vue')
},
{
path: '/about',
component: () => import('../views/About.vue')
}
]
分組打包
const routes = [
{
path: '/admin',
component: () => import(/* webpackChunkName: "admin" */ '../views/Admin.vue'),
children: [
{
path: 'users',
component: () => import(/* webpackChunkName: "admin" */ '../views/AdminUsers.vue')
}
]
}
]
滾動行為
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置(使用瀏覽器前進/後退)
if (savedPosition) {
return savedPosition
}
// 如果有 hash,滾動到該元素
if (to.hash) {
return { el: to.hash }
}
// 預設滾動到頂部
return { top: 0 }
}
})
路由過渡動畫
<template>
<RouterView v-slot="{ Component }">
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
</RouterView>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
每個路由不同的過渡
<template>
<RouterView v-slot="{ Component, route }">
<Transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</Transition>
</RouterView>
</template>
const routes = [
{
path: '/about',
component: About,
meta: { transition: 'slide' }
}
]
命名視圖
同時渲染多個視圖:
const routes = [
{
path: '/',
components: {
default: Main,
sidebar: Sidebar,
header: Header
}
}
]
<template>
<RouterView name="header" />
<RouterView name="sidebar" />
<RouterView /> <!-- 預設 -->
</template>
重定向和別名
重定向
const routes = [
// 字串
{ path: '/home', redirect: '/' },
// 命名路由
{ path: '/home', redirect: { name: 'homepage' } },
// 函式
{
path: '/search/:query',
redirect: (to) => {
return { path: '/search', query: { q: to.params.query } }
}
}
]
別名
const routes = [
{
path: '/user',
component: User,
alias: '/member' // /member 和 /user 都會渲染 User
}
]
404 頁面
const routes = [
// ... 其他路由
// 捕獲所有不匹配的路由
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
完整範例
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/Home.vue'),
meta: { title: '首頁' }
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue'),
meta: { title: '登入' }
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/Dashboard.vue'),
meta: { requiresAuth: true, title: '儀表板' },
children: [
{
path: '',
name: 'dashboard-home',
component: () => import('../views/DashboardHome.vue')
},
{
path: 'settings',
name: 'settings',
component: () => import('../views/Settings.vue')
}
]
},
{
path: '/user/:id',
name: 'user',
component: () => import('../views/User.vue'),
meta: { title: '使用者' }
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFound.vue'),
meta: { title: '頁面不存在' }
}
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) return savedPosition
return { top: 0 }
}
})
// 全域守衛
router.beforeEach((to, from) => {
// 設定頁面標題
document.title = to.meta.title || '我的網站'
// 檢查登入
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: 'login', query: { redirect: to.fullPath } }
}
})
export default router