Vue Router 教程
项目概述
Vue Router 是 Vue.js 的官方路由管理器,与 Vue.js 核心深度集成,提供了声明式的路由配置、嵌套路由、参数路由、导航守卫等功能。它使构建单页应用(SPA)变得更加简单和直观,能够轻松管理应用的导航状态和 URL 结构。
- 项目链接:https://github.com/vuejs/router
- 官方网站:https://router.vuejs.org/
- GitHub Stars:19k+
- 适用环境:Vue.js 项目、单页应用
安装设置
1. 安装 Vue Router
Vue 3
# 使用 npm
npm install vue-router@4
# 使用 yarn
yarn add vue-router@4
# 使用 pnpm
pnpm add vue-router@4Vue 2
# 使用 npm
npm install vue-router@3
# 使用 yarn
yarn add vue-router@3
# 使用 pnpm
pnpm add vue-router@32. 基本配置
Vue 3
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// 懒加载组件
component: () => import('../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
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')Vue 2
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')3. 应用模板
<!-- App.vue -->
<template>
<div>
<!-- 导航链接 -->
<nav>
<router-link to="/">首页</router-link> |
<router-link to="/about">关于我们</router-link>
</nav>
<!-- 路由视图 -->
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<!-- 或者使用 Composition API -->
<script setup>
// 组件逻辑
</script>核心功能
1. 基本路由
路由配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/about',
component: () => import('../views/AboutView.vue')
},
{
path: '/contact',
component: () => import('../views/ContactView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router导航链接
<template>
<nav>
<!-- 基本链接 -->
<router-link to="/">首页</router-link>
<!-- 命名路由 -->
<router-link :to="{ name: 'about' }">关于我们</router-link>
<!-- 带查询参数 -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">搜索 Vue</router-link>
<!-- 带哈希 -->
<router-link :to="{ path: '/about', hash: '#team' }">关于我们 - 团队</router-link>
</nav>
<router-view />
</template>2. 嵌套路由
路由配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/user',
component: () => import('../views/UserLayout.vue'),
children: [
{
path: '', // 空路径,表示 /user 的默认子路由
component: () => import('../views/UserProfile.vue')
},
{
path: 'settings', // 完整路径: /user/settings
component: () => import('../views/UserSettings.vue')
},
{
path: 'posts', // 完整路径: /user/posts
component: () => import('../views/UserPosts.vue')
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router布局组件
<!-- UserLayout.vue -->
<template>
<div class="user-layout">
<h1>用户中心</h1>
<nav>
<router-link to="/user">个人资料</router-link> |
<router-link to="/user/settings">设置</router-link> |
<router-link to="/user/posts">我的帖子</router-link>
</nav>
<div class="content">
<!-- 子路由视图 -->
<router-view />
</div>
</div>
</template>3. 参数路由
路由配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/user/:id', // 动态参数
component: () => import('../views/UserDetailView.vue'),
props: true // 将路由参数作为 props 传递给组件
},
{
path: '/product/:category/:id', // 多个动态参数
component: () => import('../views/ProductDetailView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router组件中获取参数
<!-- UserDetailView.vue -->
<template>
<div>
<h1>用户详情</h1>
<p>用户 ID: {{ id }}</p>
</div>
</template>
<!-- Option API -->
<script>
export default {
props: ['id'], // 从路由参数获取
mounted() {
// 也可以通过 this.$route.params 获取
console.log(this.$route.params.id)
}
}
</script>
<!-- 或者 Composition API -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const id = route.params.id
// 响应式获取参数
import { computed } from 'vue'
const userId = computed(() => route.params.id)
</script>4. 编程式导航
使用 router.push
<template>
<div>
<button @click="navigateToHome">返回首页</button>
<button @click="navigateToUser(1)">查看用户 1</button>
<button @click="navigateToSearch('vue')">搜索 Vue</button>
</div>
</template>
<!-- Option API -->
<script>
export default {
methods: {
navigateToHome() {
this.$router.push('/')
},
navigateToUser(id) {
// 通过路径
this.$router.push(`/user/${id}`)
// 通过命名路由
this.$router.push({
name: 'user',
params: { id }
})
},
navigateToSearch(query) {
this.$router.push({
path: '/search',
query: { q: query }
})
}
}
}
</script>
<!-- Composition API -->
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function navigateToHome() {
router.push('/')
}
function navigateToUser(id) {
router.push(`/user/${id}`)
}
function navigateToSearch(query) {
router.push({
path: '/search',
query: { q: query }
})
}
</script>其他导航方法
// 替换当前历史记录(不会添加新条目)
router.replace('/about')
// 后退一步
router.back()
// 前进一步
router.forward()
// 移动指定步数
router.go(-2) // 后退两步
router.go(1) // 前进一步5. 重定向和别名
重定向
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/home',
redirect: '/' // 重定向到首页
},
{
path: '/old-user/:id',
redirect: to => {
// 动态重定向
return {
path: `/user/${to.params.id}`,
query: to.query
}
}
}
]别名
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue'),
alias: '/home' // 别名
},
{
path: '/user/:id',
component: () => import('../views/UserDetailView.vue'),
alias: ['/profile/:id', '/member/:id'] // 多个别名
}
]6. 404 页面
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
// 其他路由...
{
// 捕获所有未匹配的路由
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../views/NotFoundView.vue')
}
]<!-- NotFoundView.vue -->
<template>
<div>
<h1>404</h1>
<p>页面不存在</p>
<router-link to="/">返回首页</router-link>
</div>
</template>高级功能
1. 导航守卫
全局前置守卫
// router/index.js
router.beforeEach((to, from, next) => {
// 检查用户是否登录
const isLoggedIn = localStorage.getItem('token')
// 需要认证的路由
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth && !isLoggedIn) {
// 未登录,重定向到登录页
next({
path: '/login',
query: { redirect: to.fullPath } // 保存重定向路径
})
} else {
// 允许导航
next()
}
})路由配置中的元信息
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/login',
component: () => import('../views/LoginView.vue')
},
{
path: '/dashboard',
component: () => import('../views/DashboardView.vue'),
meta: { requiresAuth: true } // 需要认证
}
]全局后置守卫
// router/index.js
router.afterEach((to, from) => {
// 页面标题设置
document.title = to.meta.title || '我的应用'
// 页面统计
console.log(`从 ${from.path} 导航到 ${to.path}`)
})路由独享守卫
// router/index.js
const routes = [
{
path: '/dashboard',
component: () => import('../views/DashboardView.vue'),
beforeEnter: (to, from, next) => {
// 路由独享的守卫
const isAdmin = localStorage.getItem('role') === 'admin'
if (!isAdmin) {
next('/')
} else {
next()
}
}
}
]组件内守卫
<!-- UserDetailView.vue -->
<template>
<!-- 组件内容 -->
</template>
<script>
export default {
// 进入组件前
beforeRouteEnter(to, from, next) {
// 此时组件实例还未创建
console.log('进入用户详情页')
next()
},
// 路由更新时(参数变化)
beforeRouteUpdate(to, from, next) {
// 组件实例已存在
console.log('用户 ID 已更新:', to.params.id)
this.loadUserData(to.params.id)
next()
},
// 离开组件前
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges) {
if (confirm('有未保存的更改,确定要离开吗?')) {
next()
} else {
next(false) // 取消导航
}
} else {
next()
}
},
methods: {
loadUserData(id) {
// 加载用户数据
}
}
}
</script>
<!-- 或者 Composition API -->
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
onBeforeRouteEnter((to, from, next) => {
console.log('进入用户详情页')
next()
})
onBeforeRouteUpdate((to, from, next) => {
console.log('用户 ID 已更新:', to.params.id)
loadUserData(to.params.id)
next()
})
onBeforeRouteLeave((to, from, next) => {
const hasUnsavedChanges = false // 检查是否有未保存的更改
if (hasUnsavedChanges) {
if (confirm('有未保存的更改,确定要离开吗?')) {
next()
} else {
next(false)
}
} else {
next()
}
})
function loadUserData(id) {
// 加载用户数据
}
</script>2. 路由懒加载
基本懒加载
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/about',
component: () => import('../views/AboutView.vue')
}
]带命名的懒加载
// router/index.js
const routes = [
{
path: '/',
component: () => import(/* webpackChunkName: "home" */ '../views/HomeView.vue')
},
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]3. 滚动行为
// router/index.js
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置,恢复它
if (savedPosition) {
return savedPosition
}
// 否则滚动到顶部
return { top: 0 }
// 或者滚动到指定元素
// if (to.hash) {
// return {
// selector: to.hash
// }
// }
}
})4. 路由元信息
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue'),
meta: {
title: '首页',
requiresAuth: false
}
},
{
path: '/dashboard',
component: () => import('../views/DashboardView.vue'),
meta: {
title: '仪表盘',
requiresAuth: true,
requiresAdmin: true
}
}
]
// 全局后置守卫设置标题
router.afterEach((to) => {
document.title = to.meta.title || '我的应用'
})
// 全局前置守卫检查权限
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token')
const isAdmin = localStorage.getItem('role') === 'admin'
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login')
} else if (to.meta.requiresAdmin && !isAdmin) {
next('/')
} else {
next()
}
})5. 动态路由
添加路由
// 动态添加路由
router.addRoute({
path: '/new-route',
component: () => import('../views/NewRouteView.vue')
})
// 添加嵌套路由
router.addRoute('parent', {
path: 'child',
component: () => import('../views/ChildView.vue')
})删除路由
// 通过名称删除路由
router.removeRoute('routeName')
// 通过添加一个同名路由来替换
router.addRoute({ path: '/old', name: 'routeName', component: OldComponent })
router.addRoute({ path: '/new', name: 'routeName', component: NewComponent }) // 替换实际应用场景
1. 认证系统
路由配置
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/login',
component: () => import('../views/LoginView.vue'),
meta: {
requiresGuest: true
}
},
{
path: '/register',
component: () => import('../views/RegisterView.vue'),
meta: {
requiresGuest: true
}
},
{
path: '/dashboard',
component: () => import('../views/DashboardView.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/profile',
component: () => import('../views/ProfileView.vue'),
meta: {
requiresAuth: true
}
}
]导航守卫
// router/index.js
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token')
// 检查是否需要认证
if (to.meta.requiresAuth && !isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
// 检查是否需要访客状态
else if (to.meta.requiresGuest && isLoggedIn) {
next('/dashboard')
}
else {
next()
}
})登录组件
<!-- LoginView.vue -->
<template>
<div>
<h1>登录</h1>
<form @submit.prevent="handleLogin">
<input type="text" v-model="username" placeholder="用户名" />
<input type="password" v-model="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const username = ref('')
const password = ref('')
const handleLogin = async () => {
try {
// 模拟登录
const token = 'fake-token'
localStorage.setItem('token', token)
// 登录成功后重定向
const redirectPath = route.query.redirect || '/dashboard'
router.push(redirectPath)
} catch (error) {
console.error('登录失败:', error)
}
}
</script>2. 电商应用
路由配置
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/products',
component: () => import('../views/ProductsView.vue')
},
{
path: '/product/:id',
component: () => import('../views/ProductDetailView.vue'),
props: true
},
{
path: '/cart',
component: () => import('../views/CartView.vue')
},
{
path: '/checkout',
component: () => import('../views/CheckoutView.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/order/:id',
component: () => import('../views/OrderDetailView.vue'),
meta: {
requiresAuth: true
}
}
]商品详情组件
<!-- ProductDetailView.vue -->
<template>
<div>
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>
<p>价格: ¥{{ product.price }}</p>
<button @click="addToCart">加入购物车</button>
<router-link to="/products">返回商品列表</router-link>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const product = ref({})
const id = route.params.id
onMounted(() => {
// 加载商品数据
fetchProduct(id)
})
const fetchProduct = async (productId) => {
// 模拟 API 请求
product.value = {
id: productId,
name: '示例商品',
description: '这是一个示例商品',
price: 99.99
}
}
const addToCart = () => {
// 加入购物车逻辑
console.log('加入购物车:', product.value)
router.push('/cart')
}
</script>3. 管理后台
路由配置
// router/index.js
const routes = [
{
path: '/',
component: () => import('../views/HomeView.vue')
},
{
path: '/admin',
component: () => import('../views/AdminLayout.vue'),
meta: {
requiresAuth: true,
requiresAdmin: true
},
children: [
{
path: '',
component: () => import('../views/AdminDashboardView.vue')
},
{
path: 'users',
component: () => import('../views/AdminUsersView.vue')
},
{
path: 'products',
component: () => import('../views/AdminProductsView.vue')
},
{
path: 'orders',
component: () => import('../views/AdminOrdersView.vue')
}
]
}
]管理后台布局
<!-- AdminLayout.vue -->
<template>
<div class="admin-layout">
<aside>
<h2>管理后台</h2>
<nav>
<router-link to="/admin">仪表盘</router-link>
<router-link to="/admin/users">用户管理</router-link>
<router-link to="/admin/products">商品管理</router-link>
<router-link to="/admin/orders">订单管理</router-link>
</nav>
</aside>
<main>
<router-view />
</main>
</div>
</template>代码优化建议
- 路由配置分离:将路由配置分离到单独的文件中,按功能模块组织
- 使用懒加载:对所有路由组件使用懒加载,减小初始包大小
- 导航守卫抽象:将认证逻辑抽象为可重用的导航守卫
- 嵌套路由合理使用:利用嵌套路由和布局组件减少代码重复
- 参数验证:对路由参数进行验证,确保数据安全性
- 错误处理:添加 404 页面和路由错误处理
- 性能优化:
- 使用适当的缓存策略
- 避免在路由守卫中进行昂贵的计算
- 合理使用
scrollBehavior提升用户体验
- 代码组织:
- 按功能模块组织路由和组件
- 使用一致的命名规范
- 添加适当的注释
- SEO 考虑:对于需要 SEO 的页面,考虑使用服务端渲染或预渲染
- 测试:为路由配置和导航逻辑添加测试