跳到主要内容

Vue Router 路由

Vue Router 是 Vue.js 官方的路由管理器,用于构建单页面应用(SPA)。本章将详细介绍 Vue Router 4 的使用方法。

基础概念

什么是路由?

路由是指根据 URL 路径显示不同内容的技术:

┌─────────────────────────────────────────────────────────┐
│ 路由工作原理 │
├─────────────────────────────────────────────────────────┤
│ │
│ 用户访问 /about │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Router │ 匹配路由配置 │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Component │ 渲染对应组件 │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

安装 Vue Router

使用 Vite 创建项目时可以选择安装,或手动安装:

npm install vue-router

基本使用

创建路由配置

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import User from '../views/User.vue'

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
// 动态路由参数
path: '/user/:id',
name: 'User',
component: User
}
]

const router = createRouter({
history: createWebHistory(),
routes
})

export default router

在应用中使用

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
.use(router)
.mount('#app')
<!-- App.vue -->
<script setup>
import { RouterView, RouterLink } from 'vue-router'
</script>

<template>
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</nav>

<RouterView />
</template>

路由链接

<template>
<!-- 基本用法 -->
<RouterLink to="/">首页</RouterLink>

<!-- 动态路径 -->
<RouterLink :to="{ name: 'User', params: { id: 1 }}">用户1</RouterLink>

<!-- class 绑定 -->
<RouterLink to="/" active-class="active">首页</RouterLink>

<!-- 精确匹配 -->
<RouterLink to="/" exact-active-class="exact-active">首页</RouterLink>
</template>

编程式导航

import { useRouter } from 'vue-router'

const router = useRouter()

// 跳转到指定路径
router.push('/about')

// 跳转到命名路由
router.push({ name: 'User', params: { id: 1 }})

// 跳转到带查询参数
router.push({ path: '/search', query: { q: 'vue' }})

// 替换当前记录
router.replace('/about')

// 前进/后退
router.go(1) // 前进
router.go(-1) // 后退
router.back() // 后退一步
router.forward() // 前进一步

动态路由

路由参数

// 定义带参数的路由
const routes = [
{ path: '/user/:id', component: User },
{ path: '/article/:category/:id', component: Article }
]

// 访问参数
// URL: /user/123
// params: { id: '123' }

在组件中获取参数

<script setup>
import { useRoute } from 'vue-router'
import { computed } from 'vue'

const route = useRoute()

// 获取参数
const userId = computed(() => route.params.id)

// 获取查询参数
const search = computed(() => route.query.q)

// 获取哈希值
const hash = computed(() => route.hash)
</script>

<template>
<p>用户ID: {{ userId }}</p>
<p>搜索词: {{ search }}</p>
</template>

嵌套路由

const routes = [
{
path: '/user/:id',
component: UserLayout,
children: [
{
path: '', // /user/:id
name: 'UserProfile',
component: UserProfile
},
{
path: 'posts', // /user/:id/posts
name: 'UserPosts',
component: UserPosts
},
{
path: 'settings', // /user/:id/settings
name: 'UserSettings',
component: UserSettings
}
]
}
]
<!-- UserLayout.vue -->
<template>
<div class="user-layout">
<nav>
<RouterLink :to="`/user/${userId}`">个人信息</RouterLink>
<RouterLink :to="`/user/${userId}/posts`">帖子</RouterLink>
<RouterLink :to="`/user/${userId}/settings`">设置</RouterLink>
</nav>

<RouterView />
</div>
</template>

命名路由

const routes = [
{
path: '/user/:id',
name: 'User',
component: User
}
]
<template>
<RouterLink :to="{ name: 'User', params: { id: 1 }}">用户1</RouterLink>
<button @click="goToUser(2)">用户2</button>
</template>

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

function goToUser(id) {
router.push({ name: 'User', params: { id } })
}
</script>

路由守卫

全局守卫

const router = createRouter({ ... })

// 全局前置守卫
router.beforeEach((to, from) => {
// to: 目标路由
// from: 来源路由

// 返回值:
// - true: 继续导航
// - false: 取消导航
// - RouteLocationRaw: 跳转到其他路由

if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'Login' }
}
return true
})

// 全局解析守卫
router.beforeResolve((to, from) => {
// 在组件加载前调用
})

// 全局后置守卫
router.afterEach((to, from) => {
// 导航完成后调用
})

路由独享守卫

const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// 只在进入 /admin 时调用
return true
}
}
]

组件内守卫

<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

// 进入路由前
onBeforeRouteEnter((to, from) => {
// 不能访问 this,但可以通过回调访问组件实例
// 在路由确认后,组件挂载前调用
})

// 路由更新时(同一组件,参数变化)
onBeforeRouteUpdate((to, from) => {
// 可以访问组件实例
})

// 离开路由时
onBeforeRouteLeave((to, from) => {
// 可以访问组件实例
// 常用于确认离开
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
})
</script>

路由元信息

const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
role: 'admin',
title: '管理后台'
}
}
]
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'Login' }
}

// 设置页面标题
document.title = to.meta.title || '默认标题'
})

路由懒加载

// 直接导入
import Home from './views/Home.vue'

// 懒加载
const Home = () => import('./views/Home.vue')

// 带命名 chunk 的懒加载
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue')

// 懒加载多个路由到同一 chunk
const UserPosts = () => import(/* webpackChunkName: "user" */ './views/UserPosts.vue')
const UserSettings = () => import(/* webpackChunkName: "user" */ './views/UserSettings.vue')

404 页面

const routes = [
// 其他路由
{ path: '/:pathMatch(.*)*', component: NotFound }
]
<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
</script>

<template>
<div>
<h1>404</h1>
<p>页面不存在</p>
<RouterLink to="/">返回首页</RouterLink>
</div>
</template>

过渡动画

<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;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

小结

本章我们详细学习了 Vue Router 的完整内容:

  1. 路由基础:创建路由配置和使用路由
  2. 路由链接:RouterLink 和编程式导航
  3. 动态路由:路由参数和查询参数
  4. 嵌套路由:多级路由结构
  5. 命名路由:通过名称导航
  6. 路由守卫:权限控制和数据加载
  7. 路由懒加载:优化应用性能
  8. 过渡动画:页面切换效果

练习

  1. 创建一个带侧边栏的后台管理页面
  2. 实现登录验证和权限控制
  3. 实现路由懒加载和loading状态

准备好进入下一章了吗?