目录
[一、Vue-Router 是什么?](#一、Vue-Router 是什么?)
[传统多页应用 vs 单页应用](#传统多页应用 vs 单页应用)
[三、基础使用(Vue 3)](#三、基础使用(Vue 3))
[1. 安装](#1. 安装)
[2. 配置路由](#2. 配置路由)
[history 模式对比](#history 模式对比)
[3. 注册路由](#3. 注册路由)
[4. 模板中使用](#4. 模板中使用)
[2. 第一层父组件:UserLayout](#2. 第一层父组件:UserLayout)
[3. 第二层父组件:UserProfile(个人资料布局)](#3. 第二层父组件:UserProfile(个人资料布局))
[1. 全局前置守卫(最常用)](#1. 全局前置守卫(最常用))
[2. 全局后置守卫](#2. 全局后置守卫)
[3. 路由独享守卫](#3. 路由独享守卫)
[4. 组件内守卫](#4. 组件内守卫)
Vue-Router 是 Vue.js 的官方路由管理器 ,用于构建单页应用(SPA),通过管理 URL 与组件的映射关系,实现页面切换而不刷新浏览器。
一、Vue-Router 是什么?
传统多页应用 vs 单页应用
| 对比 | 多页应用 (MPA) | 单页应用 (SPA) |
|---|---|---|
| 页面切换 | 刷新整个页面 | 无刷新,局部更新 |
| 路由方式 | 发送 HTTP 请求 | 前端路由(hash/history) |
| 用户体验 | 有白屏,体验差 | 流畅,体验好 |
| SEO | 好 | 较差(需 SSR) |
Vue-Router 的作用:在 SPA 中,根据 URL 的变化,渲染对应的组件,实现"页面切换"的体验。
二、核心概念
用户访问 /users
│
▼
Vue-Router 匹配路由规则
│
▼
渲染 Users 组件到 <router-view />
| 概念 | 说明 |
|---|---|
<router-link> |
声明式导航,生成可点击的链接 |
<router-view> |
路由出口,匹配到的组件渲染在这里 |
routes |
路由配置数组,定义路径与组件的映射 |
router.push() |
编程式导航,JS 跳转页面 |
| 路由守卫 | 导航过程中的钩子函数 |
三、基础使用(Vue 3)
1. 安装
npm install vue-router@4
2. 配置路由
javascript
// 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 //匹配后渲染的组件
}, //用户访问根路径 / 时,<router-view /> 中显示 Home.vue 组件。
{
path: '/about',
name: 'About',
component: About
},
{
path: '/user/:id', // 动态路由
name: 'User',
component: () => import('@/views/User.vue') // 懒加载(访问 /user/:id 时才加载 User.vue)
}
]
const router = createRouter({ //创建路由实例
history: createWebHistory(), // HTML5 模式
routes
})
export default router //导出路由
routes数组 :定义 URL 路径与组件的映射关系。每个路由对象包含path(路径)、name(名称)、component(组件)。动态路由 :
/user/:id中的:id是占位符,可以匹配/user/1、/user/123等,通过route.params.id获取参数。懒加载 :
() => import('...')让组件在访问时才加载,减少首屏体积。
createRouter:创建路由实例,配置历史模式(createWebHistory是干净 URL)。导出后 :在
main.js中通过app.use(router)注册,应用才能使用路由功能。
history 模式对比
| 模式 | URL 示例 | 说明 |
|---|---|---|
createWebHistory() |
http://localhost:5173/about |
干净 URL,需要服务器配置 |
createWebHashHistory() |
http://localhost:5173/#/about |
带 #,无需服务器配置 |
需要服务器配置是指:使用 History 模式时,刷新页面或直接访问子路径,服务器需要返回
index.html而不是 404。原因 :SPA 只有一个真实的
index.html,所有路由都是前端模拟的。Hash 模式下#后面的内容不发送给服务器,所以没问题。History 模式下浏览器会真实请求/about路径,服务器需要把所有请求都指向index.html,让前端路由处理。解决方案 :配置 Nginx 的
try_files $uri $uri/ /index.html;,或 Apache 的mod_rewrite,或 Node.js 的app.get('*', ...)。开发环境:Vite/Webpack 自动处理,无需配置。
打包部署到生产服务器(如 Nginx)时,需要手动配置;如果使用 Vercel、Netlify 等平台,它们也自动处理了
3. 注册路由
javascript
// main.js :Vue 应用的入口文件
import { createApp } from 'vue'
import App from './App.vue' //导入根组件,整个应用的入口组件
import router from './router' // 导入上面导出的 router
const app = createApp(App) //创建 Vue 应用实例
app.use(router) // 注册路由
app.mount('#app') //挂载
4. 模板中使用
javascript
<template>
<div>
<!-- 声明式导航 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="{ name: 'User', params: { id: 123 } }">用户123</router-link>
<!-- 路由出口 -->
<router-view />
</div>
</template>
<router-link>和<router-view>是 Vue-Router 的核心组件。
<router-link>:声明式导航,渲染成<a>标签。to属性指定跳转路径,支持字符串或对象。:to用于动态绑定,可以传对象和变量。点击时自动更新 URL,不会刷新页面。
<router-view>:路由出口,匹配到的组件会渲染在这里。一个应用可以有一个或多个(命名视图)。工作流程 :点击
<router-link>→ URL 变化 → 匹配路由配置 → 找到组件 → 渲染到<router-view>。
四、动态路由匹配
javascript
const routes = [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User },
{ path: '/post/:postId/comment/:commentId', component: Comment }
]
vue
<!-- 获取参数 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // 获取 URL 中的 id
</script>
五、嵌套路由
javascript
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配时
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配时
path: 'posts',
component: UserPosts
}
]
}
]
javascript
<!-- User.vue -->
<template>
<div>
<h1>用户页面</h1>
<router-link to="profile">个人资料</router-link>
<router-link to="posts">帖子</router-link>
<!-- 嵌套路由出口 -->
<router-view />
</div>
</template>
路由配置(三层嵌套)
javascript
// router/index.js
import UserLayout from '@/layouts/UserLayout.vue'
import UserProfile from '@/views/UserProfile.vue'
import ProfileBasic from '@/views/profile/ProfileBasic.vue'
import ProfilePassword from '@/views/profile/ProfilePassword.vue'
import ProfilePhone from '@/views/profile/ProfilePhone.vue'
const routes = [
{
path: '/user',
component: UserLayout, // 第一层父组件
children: [
{
path: 'profile', // /user/profile
component: UserProfile, // 第二层父组件
children: [ // 👈 第三层:个人资料下的子路由
{
path: '', // /user/profile → 默认显示基本信息
component: ProfileBasic
},
{
path: 'password', // /user/profile/password
component: ProfilePassword
},
{
path: 'phone', // /user/profile/phone
component: ProfilePhone
}
]
},
{
path: 'posts', // /user/posts
component: UserPosts
},
{
path: 'settings', // /user/settings
component: UserSettings
}
]
}
]
2. 第一层父组件:UserLayout
javascript
<!-- layouts/UserLayout.vue -->
<template>
<div class="user-layout">
<h1>用户中心</h1>
<!-- 第一层导航 -->
<nav class="level1-nav">
<router-link to="/user/profile">个人资料</router-link>
<router-link to="/user/posts">我的帖子</router-link>
<router-link to="/user/settings">账号设置</router-link>
</nav>
<!-- 第一层出口:显示个人资料/我的帖子/账号设置 -->
<div class="level1-content">
<router-view />
</div>
</div>
</template>
3. 第二层父组件:UserProfile(个人资料布局)
javascript
<!-- views/UserProfile.vue -->
<template>
<div class="user-profile">
<h2>个人资料</h2>
<!-- 第二层导航:个人资料下的子选项 -->
<nav class="level2-nav">
<router-link to="/user/profile">基本信息</router-link>
<router-link to="/user/profile/password">修改密码</router-link>
<router-link to="/user/profile/phone">绑定手机</router-link>
</nav>
<!-- 第二层出口:显示基本信息/修改密码/绑定手机 -->
<div class="level2-content">
<router-view />
</div>
</div>
</template>
<style scoped>
.level2-nav {
margin: 15px 0;
padding: 10px;
background: #f5f5f5;
}
.level2-nav a {
margin-right: 20px;
}
.level2-content {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
</style>
六、编程式导航
| 方式 | 是什么 | 示例 |
|---|---|---|
| 声明式导航 | 用 <router-link> 标签,像写 HTML 一样声明跳转 |
<router-link to="/about">关于</router-link> |
| 编程式导航 | 用 JavaScript 代码调用方法跳转 | router.push('/about') |
| 方法 | 作用 | 能否后退 | 示例 |
|---|---|---|---|
push() |
添加新历史记录 | ✅ 能 | router.push('/about') |
replace() |
替换当前历史记录 | ❌ 不能 | router.replace('/about') |
go() |
前进/后退 N 步 | - | router.go(-1) |
javascript
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
// 字符串路径
router.push('/users')
// 对象路径
router.push({ path: '/users' })
// 命名路由
router.push({ name: 'User', params: { id: 123 } })
// 带查询参数
router.push({ path: '/users', query: { page: 2 } })
// 替换当前历史记录(不能后退)
router.replace('/users')
// 前进/后退
router.go(1) // 前进
router.go(-1) // 后退
</script>
router.push()有四种常用写法:1. 字符串路径 :
router.push('/users'),用于简单固定路径。2. 对象路径 :
router.push({ path: '/users' }),配合query传查询参数。3. 命名路由 :
router.push({ name: 'User', params: { id: 123 } }),用于动态路由(/user/:id),更安全,路径变化时不用改代码。4. 带查询参数 :
router.push({ path: '/users', query: { page: 2 } }),用于分页、搜索、筛选。注意 :
path和params不能一起用,要用name才能配合params。
命名路由(
name + params) :需要 路由配置中有:动态路径(如/user/:id),params会替换:占位符带查询参数(
path + query) :不需要 路由配置:,query会变成 URL 问号后的参数(如?page=2)
路径参数(
/user/:id) :用来标识具体资源 ,回答'哪一个'的问题。用于详情页、编辑页等需要明确指定资源的场景。需要在路由配置中定义:占位符。问号参数(
/users?page=2) :用来筛选、排序、分页,回答'怎么展示'的问题。用于列表页、搜索页等需要控制展示方式的场景。不需要在路由配置中定义。示例:
/user/123→ 用户123的详情页(路径参数)
/users?page=2&keyword=vue→ 搜索Vue的第2页(问号参数)规范:资源标识用 params,资源属性用 query。
七、路由守卫
路由守卫 是 Vue Router 提供的导航过程中的钩子函数 ,可以在路由跳转的不同阶段 插入代码,实现权限控制、页面标题、加载进度、数据预获取等功能。
1. 全局前置守卫(最常用)
在路由跳转之前 触发,用于权限验证、登录检查。
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [...]
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to:要去的路由对象
// from:当前路由对象
// next:放行函数
const token = localStorage.getItem('token')
// 需要登录的页面
if (to.meta.requiresAuth && !token) {
next({ name: 'Login', query: { redirect: to.fullPath } })
}
// 已登录访问登录页,跳转首页
else if (to.name === 'Login' && token) {
next({ name: 'Home' })
}
else {
next() // 放行
}
})
2. 全局后置守卫
在路由跳转之后 触发,没有 next 参数,用于页面标题、埋点统计、关闭进度条。
javascript
// 全局后置守卫
router.afterEach((to, from) => {
// 设置页面标题
document.title = to.meta.title || '默认标题'
// 关闭加载进度条
NProgress.done()
// 埋点统计
if (window.gtag) {
window.gtag('config', 'UA-XXXXX-Y', { page_path: to.fullPath })
}
// 滚动到顶部
window.scrollTo(0, 0)
})
3. 路由独享守卫
只在特定路由中定义,只对该路由生效。
javascript
const routes = [
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: { requiresAuth: true, role: 'admin' },
beforeEnter: (to, from, next) => {
// 只在这个路由生效
const userRole = localStorage.getItem('role')
if (userRole === 'admin') {
next()
} else {
next({ name: 'Forbidden' })
}
}
}
]
4. 组件内守卫
在组件内部定义,与组件生命周期结合。
javascript
<script>
export default {
// 1. 进入组件前(组件实例还没创建,不能访问 this)
beforeRouteEnter(to, from, next) {
// 不能访问 this
console.log('beforeRouteEnter', to.params.id)
// 可以通过 next 回调访问组件实例
next(vm => {
// vm 是组件实例
vm.fetchData()
})
},
// 2. 路由参数变化时(如 /user/1 → /user/2)
beforeRouteUpdate(to, from, next) {
// 可以访问 this
console.log('beforeRouteUpdate', to.params.id)
this.userId = to.params.id
this.fetchData()
next()
},
// 3. 离开组件前(防止未保存的更改丢失)
beforeRouteLeave(to, from, next) {
// 可以访问 this
if (this.hasUnsavedChanges) {
const answer = confirm('有未保存的更改,确定离开吗?')
answer ? next() : next(false)
} else {
next()
}
}
}
</script>
八、路由模式对比
| 模式 | 实现方式 | URL 示例 | 兼容性 | 适用场景 |
|---|---|---|---|---|
| hash | URL 的 hash 值 | /#/users |
所有浏览器 | 无需服务端配置 |
| history | HTML5 History API | /users |
IE10+ | 需要服务端配置 |
javascript
// hash 模式
const router = createRouter({
history: createWebHashHistory()
})
// history 模式(推荐)
const router = createRouter({
history: createWebHistory()
})
history 模式需要服务端配置(否则刷新 404):
nginx
# Nginx 配置
location / {
try_files $uri $uri/ /index.html;
}
九、路由懒加载
javascript
// 非懒加载(首屏一次性加载所有页面)
import Home from '@/views/Home.vue'
// 懒加载(访问时才加载)
const Home = () => import('@/views/Home.vue')
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: () => import('@/views/About.vue') }
]
十、面试回答
"Vue-Router 是 Vue 的官方路由管理器,用于构建单页应用。
核心功能:
管理 URL 与组件的映射关系
提供
<router-link>声明式导航和router.push()编程式导航通过
<router-view>渲染匹配的组件两种模式:
hash模式:URL 带#,兼容性好,无需服务端配置
history模式:URL 干净,需要服务端配置路由守卫 :
beforeEach(全局前置)、afterEach(全局后置)、beforeEnter(路由独享)懒加载 :
() => import('@/views/Home.vue'),减少首屏体积常用场景 :权限验证(路由守卫中检查登录状态)、动态路由(
/user/:id)、嵌套路由(后台管理系统)"