Vue Router 4
1. 基础配置
1.1 路由配置
javascript
// router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
const routes = [
// 静态导入
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: { title: '关于我们' }
},
// 动态路由
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue'),
props: true // 路由参数作为 props
},
// 重定向
{
path: '/home',
redirect: '/'
},
// 别名
{
path: '/about',
alias: '/info',
component: () => import('@/views/About.vue')
},
// 404
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// 或 hash 模式 (不需要服务器配置)
// history: createWebHashHistory()
routes,
// 滚动行为
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else if (to.hash) {
return { el: to.hash }
} else {
return { top: 0 }
}
}
})
// 路由守卫
router.beforeEach((to, from) => {
// 设置页面标题
document.title = to.meta.title || '默认标题'
// 权限验证
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
})
export default router1.2 路由类型
javascript
import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory } from 'vue-router'
// Web History (需要服务器配置,支持 SEO)
// 路径: /user/123
createWebHistory()
// Hash History (不需要服务器配置,不支持 SEO)
// 路径: /#/user/123
createWebHashHistory()
// Memory History (用于 SSR 或无 DOM 环境)
createMemoryHistory()2. 路由导航
2.1 编程式导航
vue
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 字符串路径
router.push('/user/123')
// 对象路径
router.push({ path: '/user/123' })
// 命名路由 (需要定义 name)
router.push({ name: 'User', params: { id: '123' } })
// 带查询参数
router.push({ path: '/user', query: { tab: 'profile' } })
// 替换当前历史
router.replace('/home')
// 前进/后退
router.go(1) // 前进
router.go(-1) // 后退
router.back() // 后退
router.forward() // 前进
// 导航成功/失败
router.push('/user/123')
.then(() => console.log('导航成功'))
.catch((err) => {
if (err.name === 'NavigationDuplicated') {
console.log('重复导航')
}
})
</script>2.2 模板中导航
vue
<template>
<!-- 声明式导航 -->
<router-link to="/">首页</router-link>
<router-link :to="{ path: '/user', query: { id: 1 } }">用户</router-link>
<!-- replace 模式 -->
<router-link to="/home" replace>首页(替换)</router-link>
<!-- active class -->
<router-link to="/about" active-class="link-active">关于</router-link>
<!-- exact-active class (精确匹配) -->
<router-link to="/" exact-active-class="link-exact-active">首页</router-link>
</template>3. 路由信息
3.1 useRoute
vue
<script setup>
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
// 当前路径
console.log(route.path) // '/user/123'
console.log(route.fullPath) // '/user/123?tab=profile'
// 路由参数
console.log(route.params) // { id: '123' }
console.log(route.params.id) // '123'
// 查询参数
console.log(route.query) // { tab: 'profile' }
console.log(route.query.tab) // 'profile'
// meta 信息
console.log(route.meta) // { title: '用户详情', requiresAuth: true }
// 路由名称
console.log(route.name) // 'User'
// matched 路由 (匹配到的路由记录)
console.log(route.matched) // 包含所有嵌套路由记录
// 判断是否是活跃
const isActive = computed(() => route.path.startsWith('/user'))
</script>3.2 路由 meta 类型定义
typescript
// router/index.ts
import type { RouteRecordRaw } from 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
roles?: string[]
keepAlive?: boolean
}
}
const routes: RouteRecordRaw[] = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
roles: ['admin'],
keepAlive: true
}
}
]4. 嵌套路由
4.1 配置
javascript
// 路由配置
const routes = [
{
path: '/user',
component: () => import('@/views/UserLayout.vue'),
children: [
// 无 path → 作为父路由的默认插槽
{
path: '',
name: 'UserIndex',
component: () => import('@/views/user/Index.vue')
},
// /user/profile
{
path: 'profile',
name: 'UserProfile',
component: () => import('@/views/user/Profile.vue')
},
// /user/settings
{
path: 'settings',
name: 'UserSettings',
component: () => import('@/views/user/Settings.vue')
}
]
}
]4.2 嵌套路由视图
vue
<!-- UserLayout.vue -->
<template>
<div class="user-layout">
<aside class="user-sidebar">
<router-link to="/user">概览</router-link>
<router-link to="/user/profile">资料</router-link>
<router-link to="/user/settings">设置</router-link>
</aside>
<main class="user-content">
<!-- 嵌套路由内容在这里 -->
<router-view />
</main>
</div>
</template>5. 命名视图
5.1 多视图配置
vue
<!-- App.vue -->
<template>
<div id="app">
<header>
<router-view name="header" />
</header>
<main>
<router-view /> <!-- 默认视图 -->
</main>
<footer>
<router-view name="footer" />
</footer>
</div>
</template>javascript
const routes = [
{
path: '/',
components: {
default: () => import('@/views/Main.vue'),
header: () => import('@/components/Header.vue'),
footer: () => import('@/components/Footer.vue')
}
}
]6. 导航守卫
6.1 守卫类型
javascript
// 全局前置守卫 (最常用)
router.beforeEach((to, from) => {
// ...
return false // 取消导航
return { name: 'Login' } // 重定向
return true // 放行
})
// 全局解析守卫 (在组件实例化之前调用)
router.beforeResolve((to, from) => {
// ...
})
// 全局后置守卫 (不常用,没有取消机会)
router.afterEach((to, from) => {
document.title = to.meta.title || 'Vue App'
})
// 路由独享守卫
const routes = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
beforeEnter: (to, from) => {
// ...
}
}
]
// 组件内守卫
export default {
beforeRouteEnter(to, from) {
// 此时组件实例还没创建
// 可以通过 next(vm => {}) 访问组件实例
},
beforeRouteUpdate(to, from) {
// 路由参数变化,但组件被复用时调用
},
beforeRouteLeave(to, from) {
// 离开路由时调用
// 可以用于确认离开
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
}
}6.2 完整守卫流程
导航触发
↓
离开当前组件 (beforeRouteLeave)
↓
全局 beforeEach
↓
在复用组件中 (beforeRouteUpdate)
↓
路由配置 beforeEnter
↓
解析异步路由组件
↓
进入新组件 (beforeRouteEnter)
↓
全局 beforeResolve
↓
导航确认
↓
触发 DOM 更新
↓
全局 afterEach7. 路由进阶
7.1 路由懒加载
javascript
// 方式1: dynamic import (推荐)
const Home = () => import('./views/Home.vue')
// 方式2: 带名字的 chunk
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue')
// 方式3: webpack require
const Home = resolve => require(['./views/Home.vue'], resolve)
// 预加载
const Home = () => import(/* webpackPrefetch: true */ './views/Home.vue')7.2 路由元信息扩展
javascript
// 自定义字段
const routes = [
{
path: '/admin',
meta: {
title: '管理后台',
icon: 'admin-icon',
roles: ['admin'],
keepAlive: true,
breadcrumb: ['首页', '系统管理', '管理后台']
}
}
]
// 动态添加路由
router.addRoute({
path: '/dynamic',
component: () => import('@/views/Dynamic.vue')
})
// 动态添加嵌套路由
router.addRoute('parent', {
path: 'child',
component: () => import('@/views/Child.vue')
})
// 删除路由
router.removeRoute('routeName')
// 获取所有路由
console.log(router.getRoutes())7.3 history 模式配置
javascript
// Apache 配置 (.htaccess)
# RewriteEngine On
# RewriteBase /
# RewriteRule ^index\.html$ - [L]
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule . /index.html [L]
// Nginx 配置
# location / {
# try_files $uri $uri/ /index.html;
# }
// Node.js Express 配置
# const history = require('connect-history-api-fallback')
# app.use(history())8. 面试要点
8.1 hash vs history 模式
hash 模式:
- URL 带 #, 如 example.com/#/user
- 原理: 监听 hashchange 事件
- 不需要服务器配置
- 不支持 SEO
- 部署简单
history 模式:
- URL 干净, 如 example.com/user
- 原理: 监听 popstate 事件,配合 HTML5 History API
- 需要服务器配置 (所有路径回退到 index.html)
- 支持 SEO
- 更美观8.2 params vs query
params:
- URL: /user/123
- 获取: route.params.id
- 必须定义 :id 参数
- 参数改变不触发组件复用
query:
- URL: /user?id=123
- 获取: route.query.id
- 不需要预定义
- 参数改变会触发组件更新8.3 导航守卫执行顺序
1. 组件内 beforeRouteLeave
2. 全局 beforeEach
3. 复用组件 beforeRouteUpdate
4. 路由 beforeEnter
5. 解析异步路由组件
6. 组件 beforeRouteEnter
7. 全局 beforeResolve
8. 导航确认
9. DOM 更新
10. 全局 afterEach8.4 路由懒加载实现
javascript
// 原理: dynamic import
const AsyncComponent = () => import('./Component.vue')
// webpack 会生成独立的 chunk 文件
// 访问时才加载,减少首屏加载时间
// 结合 webpackChunkName 可以控制文件名
const UserProfile = () => import(/* webpackChunkName: "user" */ './UserProfile.vue')