一、Vue Router 基础概念与核心原理
1.1 路由本质与核心要素
- 本质定义:路由是URL路径与页面组件的对应关系,通过路径变化控制视图切换,实现单页应用(SPA)的无刷新页面切换。
- 核心三要素:
router-link
:声明式导航组件,替代原生<a>
标签(避免页面刷新),通过to
属性指定目标路径。router-view
:路由视图出口,匹配路径的组件会渲染到该标签位置(路由有几级嵌套,就需要几个router-view
)。- 路由实例:通过
new VueRouter()
创建,配置路径与组件的映射规则(routes
数组)。
1.2 路由模式对比(hash vs history)
路由模式决定URL的表现形式和底层实现逻辑,是面试高频考点,需重点区分:
对比维度 | hash模式(默认) | history模式(推荐) |
---|---|---|
URL表现 | 带# 号(如http://localhost:8080/#/home ) |
无# 号(如http://localhost:8080/home ) |
实现原理 | 基于window.onhashchange 事件监听# 后路径变化 |
基于HTML5 History API (pushState /popState ) |
后端依赖 | 纯前端实现,# 后路径不发送到后端,无需配置 |
路径会完整发送到后端,刷新可能触发404 |
404问题解决 | 无此问题 | 需后端配置(如Nginx):未匹配路径返回index.html |
适用场景 | 简单Demo、无需美观URL的项目 | 生产环境、对URL美观度和SEO有要求的项目 |
后端Nginx配置示例(解决history模式404)
location / {
root /usr/share/nginx/html; # 项目打包后的目录
index index.html index.htm;
try_files $uri $uri/ /index.html; # 核心配置:未匹配路径返回index.html
}
1.3 路由注册机制
Vue.use(VueRouter)
的必要性:
Vue Router是Vue生态专属插件,需通过Vue.use()
显式注册,否则路由功能无法生效(类比“插座与插头”,必须配套使用)。
注意:通用工具库(如Axios)无需此步骤,仅Vue专属插件(Vue Router、Vuex)需注册。- 注册原理:
Vue.use()
会调用插件内部的install
方法,将路由实例注入Vue全局,使所有组件可通过this.$router
访问路由实例。
二、路由核心配置流程(Vue 2 + Vue Router 3.x)
路由配置需遵循“模块化”原则,避免与main.js
混杂,标准流程如下:
2.1 标准配置步骤(五步走)
步骤1:创建路由目录与文件
在src
下新建router
文件夹,创建index.js
(路由配置主文件,Webpack默认优先查找index.js
)。
步骤2:引入依赖并注册插件
// src/router/index.js
import Vue from 'vue' // 引入Vue核心
import VueRouter from 'vue-router' // 引入Vue Router
Vue.use(VueRouter) // 注册路由插件(必须步骤)
步骤3:定义路由规则(routes
数组)
routes
是路由配置的核心,每个路由对象包含path
(路径)、component
(对应组件)等属性:
// 1. 引入组件(支持懒加载,优化首屏性能)
const Home = () => import('@/views/Home.vue') // 懒加载写法
const About = () => import('@/views/About.vue')
const User = () => import('@/views/User.vue')
// 2. 定义路由规则
const routes = [
{
path: '/', // 默认路径
redirect: '/home' // 重定向到首页(避免空白页面)
},
{
path: '/home',
name: 'Home', // 路由名称(可选,用于命名路由跳转)
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/user/:id', // 动态路由::id为动态参数
name: 'User',
component: User
}
]
步骤4:创建路由实例并导出
const router = new VueRouter({
mode: 'history', // 路由模式:hash/history(默认hash)
routes // 注入路由规则(ES6简写,等同于routes: routes)
})
export default router // 导出路由实例,供main.js引入
步骤5:注入根实例并添加router-view
- 在
main.js
中引入路由实例:
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入路由实例(自动找router/index.js)
new Vue({
router, // 注入路由实例(使所有组件可访问$router/$route)
render: h => h(App)
}).$mount('#app')
- 在
App.vue
中添加router-view
(指定组件渲染位置):
<!-- src/App.vue -->
<template>
<div id="app">
<!-- 1. 路由导航 -->
<nav>
<router-link to="/home">首页</router-link>
<router-link to="/about">关于我们</router-link>
</nav>
<!-- 2. 路由视图出口:匹配的组件会渲染到这里 -->
<router-view></router-view>
</div>
</template>
2.2 routes
数组核心属性详解
属性 | 作用与说明 | 示例 |
---|---|---|
path |
路由路径(必须以/ 开头,子路由可省略/ 自动拼接父路径) |
/home (一级路由)、child (子路由) |
component |
路径对应的组件(支持懒加载() => import() 和直接引入) |
Home 、() => import('@/views/Home.vue') |
name |
路由名称(唯一,用于命名路由跳转,解耦路径变化) | name: 'Home' |
children |
子路由配置数组(实现嵌套路由,每个子路由结构与父路由一致) | children: [{ path: 'child', component: Child }] |
redirect |
重定向(访问当前路径时自动跳转到目标路径,支持字符串/name对象) | redirect: '/home' 、redirect: { name: 'Home' } |
alias |
路由别名(给路由多个访问路径,地址栏显示别名,组件不变) | alias: '/index' (访问/index 等同于/home ) |
三、路由进阶特性(实战必备)
3.1 嵌套路由(多级路由)
嵌套路由用于实现“父组件包含子组件”的层级结构(如后台管理系统的“侧边栏+内容区”),核心是children
配置和多级router-view
。
1. 配置示例(后台管理系统)
// src/router/index.js
const Layout = () => import('@/views/Layout.vue') // 父布局组件
const Dashboard = () => import('@/views/Dashboard.vue') // 子组件1
const Settings = () => import('@/views/Settings.vue') // 子组件2
const routes = [
{
path: '/admin',
name: 'Layout',
component: Layout,
// 子路由配置:path不加/,自动拼接父路径(/admin/dashboard)
children: [
{
path: '', // 子路由默认路径(访问/admin时渲染Dashboard)
component: Dashboard
},
{
path: 'settings', // 子路由路径:完整路径为/admin/settings
name: 'Settings',
component: Settings
}
]
}
]
2. 父组件(Layout.vue
)添加子路由视图
<!-- src/views/Layout.vue -->
<template>
<div class="layout">
<!-- 侧边栏(固定父组件内容) -->
<aside>
<router-link :to="{ name: 'Dashboard' }">数据看板</router-link>
<router-link :to="{ name: 'Settings' }">系统设置</router-link>
</aside>
<!-- 子路由视图出口:子组件(Dashboard/Settings)渲染到这里 -->
<main>
<router-view></router-view>
</main>
</div>
</template>
关键注意点
- 子路由
path
不加/
:自动拼接父路由路径(如settings
→/admin/settings
);加/
则为绝对路径(需写完整路径,如/admin/settings
)。 - 每级路由需对应
router-view
:父路由组件放父级router-view
,子路由组件放子级router-view
(路由有几级,就需要几个router-view
)。
3.2 动态路由(路径参数)
动态路由用于“多个路径对应同一个组件”的场景(如用户详情页/user/1
、/user/2
均渲染User
组件),核心是通过:
声明动态参数。
1. 配置示例(用户详情页)
// src/router/index.js
const User = () => import('@/views/User.vue')
const routes = [
{
// :id为动态参数,可自定义名称(如:userId)
path: '/user/:id',
name: 'User',
component: User,
// 可选:通过props将params参数注入组件(避免在组件中写$route.params)
props: true
}
]
2. 组件中获取动态参数
<!-- src/views/User.vue -->
<template>
<div>
<!-- 方式1:直接通过$route.params获取 -->
<h1>用户ID:{{ $route.params.id }}</h1>
<!-- 方式2:通过props接收(需路由配置props: true) -->
<h1>用户ID(props):{{ id }}</h1>
</div>
</template>
<script>
export default {
name: 'User',
props: ['id'], // 接收路由注入的params参数
// 监听参数变化(组件复用场景,如从/user/1跳转到/user/2)
watch: {
$route(to, from) {
console.log('用户ID变化:', to.params.id)
}
}
}
</script>
3. 动态路由跳转方式
跳转方式 | 代码示例 | 说明 |
---|---|---|
路径字符串 | this.$router.push('/user/123') |
简单直接,适合固定路径 |
path对象 | this.$router.push({ path: '/user/123' }) |
路径需完整拼接,不支持单独传params |
name+params | this.$router.push({ name: 'User', params: { id: 123 } }) |
推荐:解耦路径,params自动拼接路径 |
3.3 查询参数(query)
查询参数用于“非路径必需的临时数据传递”(如列表筛选、分页),格式为URL?key=value&key2=value2
,无需预配置路由。
1. 跳转与参数获取示例
<!-- 跳转组件(如Home.vue) -->
<template>
<button @click="goProductList">查看商品列表</button>
</template>
<script>
export default {
methods: {
goProductList() {
// 方式1:path对象+query
this.$router.push({
path: '/product',
query: { category: 'phone', page: 1 } // 查询参数
})
// 方式2:name对象+query(推荐,解耦路径)
// this.$router.push({
// name: 'Product',
// query: { category: 'phone', page: 1 }
// })
}
}
}
</script>
<!-- 目标组件(Product.vue)获取参数 -->
<template>
<div>
<h1>筛选分类:{{ $route.query.category }}</h1>
<h1>当前页码:{{ $route.query.page }}</h1>
</div>
</template>
2. 动态路由 vs 查询参数(核心区别)
对比维度 | 动态路由(params) | 查询参数(query) |
---|---|---|
路径表现 | 是路径的一部分(/user/123 ) |
附加在URL后(/product?category=phone ) |
路由配置 | 需预定义动态参数(/user/:id ) |
无需预配置 |
参数必要性 | 必选(不传递参数会导致路由匹配失败) | 可选(不传递也能匹配路由) |
数据用途 | 标识资源(如用户ID、商品ID) | 筛选、排序、分页等临时数据 |
参数获取 | $route.params |
$route.query |
3.4 通配符与404页面
通配符*
用于匹配所有未明确定义的路由,常用来实现404页面(提升用户体验,替代空白页面)。
1. 配置示例
// src/router/index.js
const NotFound = () => import('@/views/NotFound.vue') // 404组件
const routes = [
// 其他路由配置...
// 通配符路由:必须放在最后(确保优先匹配明确路由)
{
path: '*',
component: NotFound
}
]
2. 404组件示例(NotFound.vue
)
<template>
<div class="not-found">
<h1>404 - 页面走丢了!</h1>
<p>您访问的路径:{{ $route.params.pathMatch }}</p> <!-- 获取无效路径 -->
<router-link to="/home">返回首页</router-link>
</div>
</template>
<style scoped>
.not-found {
text-align: center;
margin-top: 50px;
color: #666;
}
</style>
关键注意点
- 通配符位置:必须放在路由配置最后(旧版Vue Router 3.x严格要求,新版虽优化,但保持习惯可兼容)。
pathMatch
参数:通配符匹配时,$route.params.pathMatch
会自动包含用户访问的无效路径,可用于页面提示。
3.5 重定向与别名
1. 重定向(redirect)
重定向是“访问A路径时自动跳转到B路径”,地址栏会显示B路径的URL,适合旧路径兼容、默认页面跳转。
重定向方式 | 代码示例 | 适用场景 |
---|---|---|
字符串路径 | redirect: '/home' |
简单固定跳转 |
name对象 | redirect: { name: 'Home' } |
依赖路由名称,路径变化无需修改 |
动态函数 | redirect: to => '/home' |
复杂逻辑(如根据参数动态跳转) |
2. 别名(alias)
别名是“给路由多个访问路径”,访问别名路径时,组件不变且地址栏显示别名,适合隐藏真实路由结构、兼容旧URL。
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
alias: '/index' // 访问/index等同于访问/home,地址栏显示/index
}
]
重定向 vs 别名(核心区别)
- 重定向:URL会变化(A→B),本质是“跳转”;
- 别名:URL不变(访问别名路径),本质是“同一资源的多个入口”。
四、补充重点知识(性能与实战技巧)
4.1 路由懒加载(性能优化)
路由懒加载通过“按需加载组件”减少首屏加载时间,核心是component: () => import('组件路径')
语法(Webpack动态导入)。
1. 配置示例
// 懒加载写法(推荐)
const Home = () => import('@/views/Home.vue')
// 对比:普通导入(首屏会加载所有组件,性能差)
// import Home from '@/views/Home.vue'
const routes = [
{ path: '/home', component: Home }
]
2. 优势
- 首屏加载体积减小:仅加载当前路径的组件,而非所有组件;
- 提升首屏渲染速度:避免因组件过多导致的白屏时间过长。
4.2 路径别名@
的使用
@
是Webpack默认配置的路径别名,代表src
目录,可避免复杂的相对路径(如../../views/Home.vue
)。
1. 使用示例
// 普通相对路径(繁琐,易出错)
import Home from '../../views/Home.vue'
// @别名路径(简洁,项目结构变化无需修改)
import Home from '@/views/Home.vue'
2. 原理
Webpack配置中通过resolve.alias
定义:
// webpack.config.js(Vue CLI项目无需手动配置)
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // @指向src目录
}
}
}
4.3 路由守卫基础(权限控制)
路由守卫用于“路由跳转前/后拦截”,常实现登录验证、权限判断等功能,核心介绍全局前置守卫beforeEach
。
登录验证示例
// src/router/index.js
router.beforeEach((to, from, next) => {
// 1. 不需要登录的页面(如首页、登录页)直接放行
const whiteList = ['/home', '/login']
if (whiteList.includes(to.path)) {
return next()
}
// 2. 需要登录:判断是否有token
const token = localStorage.getItem('token')
if (token) {
next() // 有token,放行
} else {
next('/login') // 无token,跳转到登录页
}
})
to
:即将进入的目标路由对象;from
:当前离开的路由对象;next
:必须调用的函数,控制是否放行(next()
放行,next('/login')
跳转到指定路径)。
五、知识小结
知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 |
---|---|---|---|
路由模式 | hash(带#,纯前端)、history(无#,需后端配置) | history模式404问题与Nginx配置 | ⭐⭐⭐ |
嵌套路由 | children 配置子路由,多级router-view 对应层级 |
子路由路径加不加/ 的区别;router-view 数量与路由层级匹配 |
⭐⭐⭐ |
动态路由 | :param 声明参数,$route.params 获取,组件复用需监听$route |
动态路由跳转的三种方式;props: true 注入参数的用法 |
⭐⭐⭐⭐ |
查询参数 | URL?key=value ,$route.query 获取,无需预配置路由 |
与动态路由的区别(必选vs可选、路径部分vs附加部分) | ⭐⭐⭐ |
通配符与404 | * 匹配所有路径,必须放路由最后,pathMatch 获取无效路径 |
通配符位置的影响;404页面的用户体验设计 | ⭐⭐ |
重定向与别名 | 重定向(URL变化)、别名(URL不变) | 两者的本质区别;别名的实际应用场景 | ⭐⭐ |
路由懒加载 | component: () => import() 按需加载,优化首屏性能 |
与普通导入的区别;懒加载对打包体积的影响 | ⭐⭐⭐ |
路由守卫 | beforeEach 全局拦截,实现登录验证、权限控制 |
next() 函数的必选性;白名单的设计思路 |
⭐⭐⭐⭐ |
六、实战小练习
6.1 题目
- 动态路由实战:实现“商品详情页”,要求:
- 配置动态路由
/product/:productId
,对应ProductDetail
组件; - 在
Home
组件中通过router-link
和编程式导航两种方式跳转到详情页(传递productId=1001
); - 在
ProductDetail
组件中显示商品ID,并监听ID变化(从1001
跳转到1002
时打印新ID)。
- 配置动态路由
- 查询参数实战:实现“商品列表筛选”,要求:
- 配置路由
/product-list
,对应ProductList
组件; - 在
Home
组件中跳转时传递查询参数category=phone&sort=price
; - 在
ProductList
组件中显示筛选分类和排序方式,并支持通过按钮切换排序(sort=price
→sort=sales
)。
- 配置路由
- 404与重定向实战:
- 创建
NotFound
组件,配置通配符路由实现404页面; - 配置重定向:访问
/
时跳转到/home
,访问/old-home
时跳转到/home
(通过name对象)。
- 创建
- 嵌套路由实战:实现“个人中心”嵌套结构,要求:
- 父路由
/profile
对应ProfileLayout
组件(包含“基本信息”“我的订单”导航); - 子路由
/profile/basic
对应ProfileBasic
组件,/profile/orders
对应ProfileOrders
组件; - 访问
/profile
时默认显示ProfileBasic
组件。
- 父路由
6.2 参考答案
1. 动态路由实战
(1)路由配置(src/router/index.js
)
const Home = () => import('@/views/Home.vue')
const ProductDetail = () => import('@/views/ProductDetail.vue')
const routes = [
{ path: '/home', name: 'Home', component: Home },
{
path: '/product/:productId',
name: 'ProductDetail',
component: ProductDetail,
props: true // 注入productId到props
}
]
(2)跳转组件(Home.vue
)
<template>
<div>
<h1>首页</h1>
<!-- 方式1:router-link跳转 -->
<router-link :to="{ name: 'ProductDetail', params: { productId: 1001 } }">
查看商品1001(router-link)
</router-link>
<!-- 方式2:编程式导航 -->
<button @click="goToProduct(1001)">查看商品1001(编程式)</button>
</div>
</template>
<script>
export default {
methods: {
goToProduct(id) {
this.$router.push({ name: 'ProductDetail', params: { productId: id } })
}
}
}
</script>
(3)详情组件(ProductDetail.vue
)
<template>
<div>
<h1>商品详情页</h1>
<p>商品ID:{{ productId }}</p>
<button @click="goToNextProduct">切换到商品1002</button>
</div>
</template>
<script>
export default {
name: 'ProductDetail',
props: ['productId'], // 接收路由注入的参数
methods: {
goToNextProduct() {
this.$router.push({ name: 'ProductDetail', params: { productId: 1002 } })
}
},
// 监听参数变化
watch: {
productId(newId, oldId) {
console.log(`商品ID从${oldId}变为${newId}`)
}
}
}
</script>
2. 查询参数实战
(1)路由配置(src/router/index.js
)
const ProductList = () => import('@/views/ProductList.vue')
const routes = [
{ path: '/product-list', name: 'ProductList', component: ProductList }
]
(2)跳转组件(Home.vue
)
<template>
<button @click="goToProductList">查看手机商品列表</button>
</template>
<script>
export default {
methods: {
goToProductList() {
this.$router.push({
name: 'ProductList',
query: { category: 'phone', sort: 'price' }
})
}
}
}
</script>
(3)列表组件(ProductList.vue
)
<template>
<div>
<h1>商品列表</h1>
<p>筛选分类:{{ $route.query.category }}</p>
<p>排序方式:{{ $route.query.sort }}</p>
<button @click="changeSort">切换排序为“销量”</button>
</div>
</template>
<script>
export default {
methods: {
changeSort() {
// 保留当前category参数,仅修改sort
this.$router.push({
name: 'ProductList',
query: { ...this.$route.query, sort: 'sales' }
})
}
}
}
</script>
3. 404与重定向实战
(1)404组件(NotFound.vue
)
<template>
<div style="text-align: center; margin-top: 50px;">
<h1 style="color: #f44336;">404 - 页面不存在</h1>
<p>您访问的路径:{{ $route.params.pathMatch }}</p>
<router-link to="/home" style="color: #2196f3;">返回首页</router-link>
</div>
</template>
(2)路由配置(src/router/index.js
)
const NotFound = () => import('@/views/NotFound.vue')
const routes = [
// 重定向:/ → /home,/old-home → /home
{ path: '/', redirect: '/home' },
{ path: '/old-home', redirect: { name: 'Home' } },
{ path: '/home', name: 'Home', component: Home },
// 通配符路由放最后
{ path: '*', component: NotFound }
]
4. 嵌套路由实战
(1)路由配置(src/router/index.js
)
const ProfileLayout = () => import('@/views/ProfileLayout.vue')
const ProfileBasic = () => import('@/views/ProfileBasic.vue')
const ProfileOrders = () => import('@/views/ProfileOrders.vue')
const routes = [
{
path: '/profile',
name: 'ProfileLayout',
component: ProfileLayout,
children: [
{ path: '', component: ProfileBasic }, // 默认显示基本信息
{ path: 'basic', name: 'ProfileBasic', component: ProfileBasic },
{ path: 'orders', name: 'ProfileOrders', component: ProfileOrders }
]
}
]
(2)父布局组件(ProfileLayout.vue
)
<template>
<div class="profile-layout">
<!-- 侧边导航 -->
<aside>
<router-link :to="{ name: 'ProfileBasic' }">基本信息</router-link>
<router-link :to="{ name: 'ProfileOrders' }">我的订单</router-link>
</aside>
<!-- 子路由视图出口 -->
<main>
<router-view></router-view>
</main>
</div>
</template>
<style scoped>
.profile-layout {
display: flex;
}
aside {
width: 200px;
border-right: 1px solid #eee;
padding: 20px;
}
aside router-link {
display: block;
margin: 10px 0;
text-decoration: none;
}
main {
flex: 1;
padding: 20px;
}
</style>
(3)子组件(ProfileBasic.vue
示例)
<template>
<div>
<h2>基本信息</h2>
<p>用户名:张三</p>
<p>手机号:138****1234</p>
</div>
</template>