文章目录
正文
1. Vue Router 简介
Vue Router 是 Vue.js 官方的路由管理器,它与 Vue.js 核心深度集成,使构建单页面应用变得轻而易举。它提供了声明式的导航控制和组件系统,让开发者能够通过配置路由映射、嵌套路由等方式构建复杂的应用。
1.1 单页应用与路由
单页应用(SPA)是一种网页应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器加载整个新页面。这种方法避免了页面之间切换打断用户体验。
// 传统多页应用
// 每次导航都会向服务器请求新页面
location.href = '/about.html'
// 单页应用
// 只改变 URL,不重新加载页面
history.pushState(null, '', '/about')
1.2 Vue Router 的核心功能
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式
- 自定义的滚动行为
2. Vue Router 基础配置
2.1 安装与基本设置
# Vue 2
npm install vue-router@3
# Vue 3
npm install vue-router@4
基本配置(Vue 3):
// 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
在 Vue 应用中使用:
// 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')
2.2 路由模式
Vue Router 提供两种路由模式:
- Hash 模式:使用 URL 的 hash (#) 来模拟完整的 URL
- History 模式:利用 HTML5 History API 实现无 hash 的 URL
// Hash 模式 (默认)
const router = createRouter({
history: createWebHashHistory(),
routes
})
// History 模式
const router = createRouter({
history: createWebHistory(),
routes
})
2.3 路由组件与视图
在 Vue 组件中使用路由视图:
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<!-- 路由匹配的组件将渲染在这里 -->
<router-view />
</div>
</template>
3. 路由导航
3.1 声明式导航
使用 <router-link>
组件进行声明式导航:
<!-- 基本用法 -->
<router-link to="/">Home</router-link>
<!-- 命名路由 -->
<router-link :to="{ name: 'About' }">About</router-link>
<!-- 带参数的路由 -->
<router-link :to="{ name: 'User', params: { id: 123 }}">User</router-link>
<!-- 带查询参数 -->
<router-link :to="{ path: '/search', query: { q: 'vue' }}">Search</router-link>
<!-- 自定义激活类 -->
<router-link to="/about" active-class="active">About</router-link>
<!-- 精确匹配 (Vue 3) -->
<router-link to="/" :exact="true">Home</router-link>
3.2 编程式导航
通过 JavaScript 代码控制路由:
// 基本导航
this.$router.push('/')
this.$router.push('/about')
// 对象形式
this.$router.push({ path: '/about' })
// 命名路由
this.$router.push({ name: 'User', params: { id: 123 }})
// 带查询参数
this.$router.push({ path: '/search', query: { q: 'vue' }})
// 替换当前历史记录
this.$router.replace('/about')
// 前进/后退
this.$router.go(1) // 前进一步
this.$router.go(-1) // 后退一步
this.$router.back() // 后退一步
this.$router.forward() // 前进一步
在 Vue 3 Composition API 中:
import { useRouter } from 'vue-router'
export default {
setup() {
const router = useRouter()
function navigateToAbout() {
router.push('/about')
}
return { navigateToAbout }
}
}
3.3 路由参数获取
通过 $route
对象访问路由参数:
// 路由配置
const routes = [
{
path: '/user/:id',
name: 'User',
component: User
}
]
// 在组件中获取参数 (Vue 2)
export default {
created() {
// 路径参数
console.log(this.$route.params.id)
// 查询参数
console.log(this.$route.query.search)
}
}
// 在组件中获取参数 (Vue 3 Composition API)
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
console.log(route.params.id)
console.log(route.query.search)
}
}
4. 高级路由配置
4.1 嵌套路由
路由可以嵌套,实现复杂的应用布局:
const routes = [
{
path: '/user',
component: User,
children: [
// 当 /user 匹配成功
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },
// 当 /user/profile 匹配成功
// UserProfile 会被渲染在 User 的 <router-view> 中
{ path: 'profile', component: UserProfile },
// 当 /user/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
{ path: 'posts', component: UserPosts }
]
}
]
对应的组件结构:
<!-- User.vue -->
<template>
<div>
<h2>User Section</h2>
<nav>
<router-link to="/user">Home</router-link>
<router-link to="/user/profile">Profile</router-link>
<router-link to="/user/posts">Posts</router-link>
</nav>
<router-view></router-view>
</div>
</template>
4.2 命名视图
同一个路由可以显示多个视图:
const routes = [
{
path: '/',
components: {
default: Home,
sidebar: Sidebar,
footer: Footer
}
}
]
对应的模板:
<router-view></router-view>
<router-view name="sidebar"></router-view>
<router-view name="footer"></router-view>
4.3 路由懒加载
为了提高应用性能,可以使用动态导入实现路由懒加载:
// 不使用懒加载
import UserDetails from './views/UserDetails.vue'
const routes = [
{ path: '/user/:id', component: UserDetails }
]
// 使用懒加载
const routes = [
{
path: '/user/:id',
component: () => import('./views/UserDetails.vue')
}
]
// 分组懒加载
const routes = [
{
path: '/user',
component: () => import(/* webpackChunkName: "user" */ './views/User.vue'),
children: [
{
path: 'profile',
component: () => import(/* webpackChunkName: "user" */ './views/UserProfile.vue')
},
{
path: 'posts',
component: () => import(/* webpackChunkName: "user" */ './views/UserPosts.vue')
}
]
}
]
5. 路由守卫
5.1 全局守卫
全局前置守卫:
router.beforeEach((to, from, next) => {
// to: 即将进入的目标路由对象
// from: 当前导航正要离开的路由对象
// next: 函数,必须调用该方法来 resolve 这个钩子
// 检查用户是否已登录
if (to.meta.requiresAuth && !isAuthenticated) {
// 未登录,重定向到登录页
next({ name: 'Login' })
} else {
// 继续导航
next()
}
})
全局解析守卫:
router.beforeResolve((to, from, next) => {
// 在所有组件内守卫和异步路由组件被解析之后调用
next()
})
全局后置钩子:
router.afterEach((to, from) => {
// 不会影响导航本身,常用于分析、更改页面标题等
document.title = to.meta.title || 'My App'
})
5.2 路由独享守卫
直接在路由配置上定义:
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
// 检查用户权限
if (hasAdminRights()) {
next()
} else {
next('/403')
}
}
}
]
5.3 组件内守卫
在组件内定义路由导航守卫:
export default {
// 在渲染该组件的对应路由被验证前调用
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 `this`
// 因为当守卫执行时,组件实例还没被创建
next(vm => {
// 通过 `vm` 访问组件实例
})
},
// 在当前路由改变,但是该组件被复用时调用
beforeRouteUpdate(to, from, next) {
// 可以访问组件实例 `this`
this.name = to.params.name
next()
},
// 导航离开该组件的对应路由时调用
beforeRouteLeave(to, from, next) {
// 可以访问组件实例 `this`
const answer = window.confirm('Do you really want to leave?')
if (answer) {
next()
} else {
next(false)
}
}
}
在 Vue 3 Composition API 中:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
export default {
setup() {
// 与 beforeRouteLeave 相同
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('Do you really want to leave?')
if (!answer) return false
})
// 与 beforeRouteUpdate 相同
onBeforeRouteUpdate((to, from) => {
// 处理路由变化...
})
}
}
6. 路由元信息与数据获取
6.1 路由元信息
可以在路由配置中附加自定义数据:
const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: 'Admin Panel',
permissions: ['admin', 'editor']
}
}
]
使用元信息:
// 全局守卫中使用
router.beforeEach((to, from, next) => {
document.title = to.meta.title || 'Default Title'
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})
// 组件中使用
export default {
created() {
console.log(this.$route.meta.permissions)
}
}
6.2 路由数据获取
在导航完成前获取数据:
export default {
data() {
return {
post: null,
loading: false,
error: null
}
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.fetchData()
})
},
beforeRouteUpdate(to, from, next) {
this.post = null
this.fetchData()
next()
},
methods: {
fetchData() {
this.loading = true
this.error = null
// 假设 getPost 返回一个 Promise
getPost(this.$route.params.id)
.then(post => {
this.post = post
this.loading = false
})
.catch(err => {
this.error = err.toString()
this.loading = false
})
}
}
}
在导航完成后获取数据:
export default {
data() {
return {
post: null,
loading: false,
error: null
}
},
created() {
// 组件创建完后获取数据
this.fetchData()
},
watch: {
// 路由变化时重新获取数据
'$route': 'fetchData'
},
methods: {
fetchData() {
this.loading = true
this.error = null
getPost(this.$route.params.id)
.then(post => {
this.post = post
this.loading = false
})
.catch(err => {
this.error = err.toString()
this.loading = false
})
}
}
}
在 Vue 3 Composition API 中:
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
const post = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
post.value = await getPost(route.params.id)
} catch (err) {
error.value = err.toString()
} finally {
loading.value = false
}
}
// 首次加载
fetchData()
// 路由参数变化时重新获取
watch(() => route.params.id, fetchData)
return { post, loading, error }
}
}
7. 动态路由与进阶技巧
7.1 动态添加和删除路由
Vue Router 允许动态修改路由配置:
// 添加路由
router.addRoute({
path: '/dynamic',
name: 'Dynamic',
component: () => import('./views/Dynamic.vue')
})
// 添加嵌套路由
router.addRoute('User', {
path: 'settings',
component: UserSettings
})
// 删除路由
// 方法 1:添加一个名字相同的路由
router.addRoute({ path: '/about', name: 'About', component: About })
// 方法 2:通过返回的删除函数
const removeRoute = router.addRoute(route)
removeRoute() // 删除路由
// 方法 3:通过名称删除
router.removeRoute('About')
// 检查路由是否存在
router.hasRoute('About')
// 获取所有路由记录
router.getRoutes()
7.2 路由过渡效果
结合 Vue 的过渡系统实现路由切换动画:
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</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>
基于路由的动态过渡:
<template>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
</template>
<script>
export default {
// 路由配置
// {
// path: '/slide-page',
// component: SlidePage,
// meta: { transition: 'slide' }
// }
}
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s;
}
.slide-enter-from,
.slide-leave-to {
transform: translateX(100%);
}
</style>
7.3 滚动行为
控制页面切换时的滚动位置:
const router = createRouter({
history: createWebHistory(),
routes: [...],
scrollBehavior(to, from, savedPosition) {
// 返回 savedPosition,在按下 后退/前进 按钮时有效
if (savedPosition) {
return savedPosition
}
// 滚动到锚点
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
}
}
// 滚动到顶部
return { top: 0 }
}
})
8. 实际应用与最佳实践
8.1 路由组织与模块化
大型应用中的路由组织:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import homeRoutes from './modules/home'
import userRoutes from './modules/user'
import shopRoutes from './modules/shop'
const routes = [
...homeRoutes,
...userRoutes,
...shopRoutes,
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('../views/NotFound.vue') }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
// router/modules/user.js
export default [
{
path: '/user',
component: () => import('@/layouts/UserLayout.vue'),
children: [
{
path: '',
name: 'UserHome',
component: () => import('@/views/user/Home.vue')
},
{
path: 'profile',
name: 'UserProfile',
component: () => import('@/views/user/Profile.vue')
}
]
}
]
8.2 权限控制与认证
基于角色的路由访问控制:
// 路由配置
const routes = [
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true, roles: ['admin'] }
},
{
path: '/editor',
component: Editor,
meta: { requiresAuth: true, roles: ['editor', 'admin'] }
}
]
// 全局前置守卫
router.beforeEach((to, from, next) => {
const { requiresAuth, roles } = to.meta
const currentUser = getCurrentUser()
if (!requiresAuth) {
return next()
}
if (!currentUser) {
return next({
path: '/login',
query: { redirect: to.fullPath }
})
}
if (roles && !roles.includes(currentUser.role)) {
return next('/403')
}
next()
})
8.3 性能优化策略
- 路由懒加载:减小初始加载体积
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
]
- 预加载关键路由:提高用户体验
// 在应用空闲时预加载
const PreloadedView = () => ({
component: import('./views/PreloadedView.vue'),
loading: LoadingComponent,
delay: 200
})
- 保持路由组件状态:使用
<keep-alive>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
- 路由元信息缓存控制:
const routes = [
{
path: '/user/:id',
component: UserDetails,
meta: { keepAlive: true }
}
]
// 在模板中
<router-view v-slot="{ Component }">
<keep-alive>
<component
v-if="$route.meta.keepAlive"
:is="Component"
/>
</keep-alive>
<component
v-if="!$route.meta.keepAlive"
:is="Component"
/>
</router-view>
9. Vue Router 与状态管理的结合
9.1 Vue Router 与 Vuex/Pinia 协作
// store/index.js (Vuex)
export default createStore({
state: {
user: null,
isAuthenticated: false
},
mutations: {
setUser(state, user) {
state.user = user
state.isAuthenticated = !!user
}
},
actions: {
async login({ commit }, credentials) {
const user = await authService.login(credentials)
commit('setUser', user)
return user
},
async logout({ commit }) {
await authService.logout()
commit('setUser', null)
}
}
})
// 路由守卫中使用
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const { isAuthenticated } = store.state
if (requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
9.2 路由参数同步到状态
// store/index.js
export default createStore({
state: {
searchQuery: '',
currentProductId: null
},
mutations: {
setSearchQuery(state, query) {
state.searchQuery = query
},
setCurrentProductId(state, id) {
state.currentProductId = id
}
}
})
// 组件中
export default {
created() {
// 从路由同步到状态
this.$store.commit('setSearchQuery', this.$route.query.q || '')
this.$store.commit('setCurrentProductId', this.$route.params.id)
},
watch: {
'$route'(to) {
this.$store.commit('setSearchQuery', to.query.q || '')
this.$store.commit('setCurrentProductId', to.params.id)
}
}
}
9.3 基于状态的导航守卫
router.beforeEach(async (to, from, next) => {
// 检查是否需要获取用户信息
if (
to.matched.some(record => record.meta.requiresAuth) &&
store.state.user === null
) {
try {
// 尝试获取用户信息
await store.dispatch('fetchCurrentUser')
next()
} catch (error) {
next('/login')
}
} else {
next()
}
})
10. 测试与调试
10.1 路由单元测试
使用 Vue Test Utils 测试路由组件:
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import App from '@/App.vue'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
// 创建路由实例
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
const router = createRouter({
history: createWebHistory(),
routes
})
test('routing', async () => {
// 挂载应用
const wrapper = mount(App, {
global: {
plugins: [router]
}
})
// 等待路由准备就绪
await router.isReady()
// 验证初始路由
expect(wrapper.html()).toContain('Welcome to Home')
// 导航到 About 页面
await router.push('/about')
// 等待路由变化
await wrapper.vm.$nextTick()
// 验证新路由内容
expect(wrapper.html()).toContain('About Page')
})
10.2 路由调试技巧
-
使用 Vue Devtools 检查路由状态
-
路由日志记录:
router.beforeEach((to, from, next) => {
console.log(`Navigating from ${from.fullPath} to ${to.fullPath}`)
next()
})
- 路由错误处理:
router.onError((error) => {
console.error('Router error:', error)
// 可以上报到错误监控系统
})
10.3 常见问题与解决方案
- 页面刷新 404 问题:
在使用 history 模式时,需要服务器配置将所有请求重定向到 index.html
# Nginx 配置
location / {
try_files $uri $uri/ /index.html;
}
- 路由参数变化但组件不更新:
使用 watch 监听路由变化或使用 key 强制更新
<router-view :key="$route.fullPath"></router-view>
- 路由守卫中的异步操作:
确保在异步操作完成后调用 next()
router.beforeEach(async (to, from, next) => {
try {
await someAsyncOperation()
next()
} catch (error) {
next(false)
}
})
- 导航重复错误:
处理导航到当前位置的情况
// Vue Router 4.x
router.push('/current-path').catch(err => {
if (err.name !== 'NavigationDuplicated') {
throw err
}
})
// 或全局处理
const originalPush = router.push
router.push = function push(location) {
return originalPush.call(this, location).catch(err => {
if (err.name !== 'NavigationDuplicated') throw err
})
}
结语
感谢您的阅读!期待您的一键三连!欢迎指正!