Vue路由
# Vue路由
# 介绍
Vue Router是Vue官方的路由管理库,用于构建单页面应用,使其能通过URL路径切换不同的组件,而无需刷新整个页面。
# 基本概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 路由(Route) | 路径规则:URL → 组件 | { path: '/about', component: About } |
| 路由器(Router) | 管理所有路由的中心 | createRouter({ routes }) |
| 路由出口(RouterView) | 页面中显示匹配组件的位置 | <router-view /> |
# 基本使用
- 安装插件
# 如果创建项目时没有安装,则需要手动安装
npm install vue-router
2
- 创建路由配置
src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
// 定义路由规则:路由路径 → 组件
const routes = [
{
// 名称(唯一标识符),可在路由跳转时使用名称进行跳转
// 跳转时使用名称的好处是无需知道路由路径,并且如果修改路由路径,跳转代码无需改动
name: 'home',
path: '/',
component: HomeView
},
{
name: 'about',
path: '/about',
component: AboutView
}
]
// 创建路由器实例
const router = createRouter({
// HTML5 History 模式(无#)
history: createWebHistory(),
// 传入上面定义的路由规则
routes
})
// 导出路由器实例,供main.js挂载
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 在main.js中挂载
src/main.js
// 引入路由
import router from './router'
// 在vue应用实例中挂载路由
app.use(router)
2
3
4
5
- 在App.vue中使用
src/App.vue
<template>
<div>
<!-- 导航菜单 -->
<nav>
<!-- 声明式导航:通过点击跳转到指定路径 -->
<router-link to="/">首页</router-link>
</nav>
<!-- 路由出口:路由匹配的组件会在这里显示 -->
<router-view />
</div>
</template>
2
3
4
5
6
7
8
9
10
11
# 路由懒加载
按需加载组件,减小首屏体积。
// 组件通过匿名函数导入,只有在访问到路径时,才会加载对应组件进行渲染
const routes = [
{ path: '/', component: () => import('@/views/Home.vue') },
{ path: '/about', component: () => import('@/views/About.vue') }
]
2
3
4
5
# 路由工作模式
在Vue Router中,路由有两种工作模式。
# Hash模式
利用URL中的#部分来模拟路由变化,#后的内容不会发送给服务器,仅在前端处理。
优点是兼容性好,后端无需配置,所有请求都指向根路径 / 即可。缺点是URL不美观,且不符合RESTful风格规范。
例如:
https://example.com/#/home第一次访问时,如果没有使用懒加载,则会下载单页面文件和所有页面组件JS文件,跳转已下载的组件页面时,就无需请求服务器,只需要在前端处理。
建议定义路由规则时使用懒加载,这样在访问到页面时才会去下载对应页面的组件JS文件。
// 路由模式配置
// 在创建路由实例时指定
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
// 使用Hash模式
history: createWebHashHistory(),
routes: [...]
})
2
3
4
5
6
7
8
9
# History模式(推荐)
URL看起来像真实的路径,且是无刷新跳转,通过 <router-link> 或 router.push() 跳转时,则会在路由变化由前端拦截,不向服务器发起新请求。
优点是URL 干净、美观、语义化,符合现代Web应用标准,缺点是需要后端fallback配置。
例如:
https://example.com/home和hash模式的区别只在URL形式和服务器配置,文件加载方式是一样的,所以也建议定义路由规则时使用懒加载。
// 路由模式配置
// 在创建路由实例时指定
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
// 使用History模式
history: createWebHistory(),
routes: [...]
})
2
3
4
5
6
7
8
9
# nginx配置
server {
listen 80;
server_name example.com;
index index.html;
# Vue 项目构建后的 dist 目录
root /path/to/your/dist;
# 核心配置:所有前端路由fallback到index.html
location / {
# $uri:先尝试匹配真实文件
# $uri/:再尝试匹配真实目录
# /index.html:如果前面都找不到则返回index.html由前端路由接管
try_files $uri $uri/ /index.html;
}
# 可选:静态资源缓存优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 导航方式
# 声明式导航
声明式导航就是在html中通过点击标签控制跳转。
会自动添加
.router-link-active类(可用于高亮当前页)。
<!-- 前进跳转,同等于a标签跳转,但不会刷新页面 -->
<router-link to="/about">关于</router-link>
<!-- 除了指定路由路径,还可以指定路由名称 -->
<router-link :to="{ name: 'about' }">关于</router-link>
<!-- 替换当前页跳转 -->
<router-link replace to="/about">关于</router-link>
2
3
4
5
6
7
8
# 编程式导航
编程式导航就是通过JS代码控制跳转。
// 需要先获取路由实例
import { useRouter } from 'vue-router'
const router = useRouter()
2
3
# 前进跳转
router.push(location) 会添加历史记录,可通过回退返回上一页。
相当于点击
<router-link to="...">
// 通过路由路径
router.push('/about')
// 通过路由名称
router.push({name: 'about'})
2
3
4
5
# 替换当前页跳转
router.replace(location) 会替换当前历史记录,无法返回上一页。
相当于点击
<router-link replace to="...">例如:登录成功后替换掉登录页,防止用户点后退回到登录页。
// 通过路由路径
router.replace('/home')
2
# 在历史记录中前进/后退
router.go(n) 参数n为正数是前进,为负数是后退。
// 前进一页,同等于router.forward()
router.go(1)
// 后退一页,同等于router.back()
router.go(-1)
// 后退两页
router.go(-2)
2
3
4
5
6
# 路由传参
vue-router还支持路由传参,提供以下 3 种主流传参方式:
# 动态路径参数(params)
参数是路径的一部分,适用于资源 ID、详情页标识等。
URL例如:
/user/123
// 定义路由,先在路由规则路径中定义参数占位符
{
// :id 是参数占位符
// :id? 如果占位符后跟?号则表示是可选参数
path: '/article/:id',
name: 'ArticleDetail',
component: ArticleDetail
}
// 跳转时传参,建议使用name+params,如果要使用路径则需要手动将参数拼接到路径上,所以不建议
// 会生成路径:/article/456
router.push({
name: 'ArticleDetail',
params: { id: 456 }
})
// 接收参数,在路由所指向的组件中接收参数
import { useRoute } from 'vue-router'
const route = useRoute()
// 始终会接收为字符串类型
console.log(route.params.id) // "456"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查询参数(query)
附加在 ? 后,类似GET请求,适用于搜索、分页、筛选条件等。
URL例如:
/search?q=vue&page=2
// 定义路由,无需特殊声明
{
path: '/productlist',
name: 'ProductList',
component: ProductList
}
// 跳转时传参,可用name或path
// 会生成路径:/product-list?category=other&page=2&sort=price
router.push({
name: 'ProductList',
query: {
category: 'other',
page: 2,
sort: 'price'
}
})
// 接收参数,在路由所指向的组件中接收参数
import { useRoute } from 'vue-router'
const route = useRoute()
// 始终会接收为字符串类型
console.log(route.query.category) // "other"
console.log(route.query.page) // "2"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 路由状态(state)
不显示在URL中,仅内存传递,适用于敏感数据、临时状态等。
URL不变。
// 跳转时传参,可用name或path
router.push({
name: 'Checkout',
state: {
cartItems: [{ id: 1, name: 'Vue Book' }],
total: 99.9
}
})
// 接收参数,在路由所指向的组件中接收参数
import { useRoute } from 'vue-router'
const route = useRoute()
// 会保持参数传入时的数据类型
console.log(route.state.total)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 路由嵌套
# 介绍
路由嵌套用于构建具有父子页面结构的复杂界面。父路由组件中包含 <router-view />,子路由的组件会渲染在这个位置。
路由还支持多层级嵌套,即子路由中也可以使用children定义嵌套路由,不过需要每一层父组件都包含 <router-view /> 否则不会渲染子路由。
# 使用方式
1.定义嵌套路由
router/index.js
const routes = [
{
// 父组件路由
path: '/user',
component: UserLayout,
// 使用children定义子组件的路由,子路由的path不需要写"/"开头
children: [
{
// 空路径,即默认子路由,匹配路径/user
path: '',
name: 'UserProfile',
component: UserProfile
},
{
// 完整路径是父路由+子路由,匹配路径/user/settings
path: 'settings',
name: 'UserSettings',
component: UserSettings
},
{
// 动态子路由,会接收参数
path: 'posts/:id',
name: 'UserPostDetail',
component: UserPostDetail
}
]
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2.父组件中包含 <router-view />
UserLayout.vue
<template>
<div class="user-layout">
<!-- 公共部分:导航菜单 -->
<nav>
<router-link to="/user">个人资料</router-link>
<router-link to="/user/settings">设置</router-link>
</nav>
<!-- 子路由组件将在这里渲染 -->
<router-view />
</div>
</template>
2
3
4
5
6
7
8
9
10
11
# 路由守卫
路由守卫用于实现导航控制,能在路由跳转的各个阶段执行逻辑。
# 路由守卫的分类
| 类型 | 作用范围 | 典型用途 |
|---|---|---|
| 1. 全局守卫 | 所有路由跳转 | 登录验证 (未登录用户不能访问后台),全局Loading动画 |
| 2. 路由独享守卫 | 单个路由配置中 | 特定页面权限验证 (例如管理员后台权限验证) |
| 3. 组件内守卫 | 单个组件内部 | 数据获取、离开确认 (表单未保存时提示用户) |
执行顺序:
beforeEach→ 路由独享beforeEnter→beforeResolve→ 组件内beforeRouteEnter→ 导航确认 → 组件内(beforeRouteUpdate/beforeRouteLeave)
# 全局前置守卫
router.beforeEach(to, from) 在路由实例中添加全局前置守卫函数。
在每次导航开始前调用,且可中断导航。
可以用于权限控制、开启全局Loading动画等。
// router/index.js
// 路由定义
{
path: '/admin',
component: Admin,
// 自定义元信息
meta: { requiresAuth: true }
}
// 添加全局前置守卫
router.beforeEach((to, from) => {
// 获取路由定义中的元数据,判断目标路由是否需要认证
if (to.meta.requiresAuth && !isAuthenticated) {
// 返回false表示取消导航
// 返回undefined或true表示允许导航
// 返回路径则表示中断当前导航,重定向到新路径
return '/login'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 全局解析守卫
router.beforeResolve(to, from) 在路由实例中添加全局解析守卫函数。
在所有组件内守卫和异步路由被解析后,导航确认前调用。
可用于确保数据加载完再显示页面。
router.beforeResolve(async (to, from) => {
if (to.meta.requiresData) {
try {
// 等待数据加载
await fetchData()
} catch (error) {
// 加载失败跳转错误页
return '/error'
}
}
})
2
3
4
5
6
7
8
9
10
11
# 全局后置守卫
router.afterEach(to, from) 在路由实例中添加全局后置守卫函数。
导航完成后调用,不能中断导航。
可以用于设置页面标题、埋点统计、关闭全局Loading动画等。
router.afterEach((to) => {
// 设置页面标题
document.title = to.meta.title || 'My App'
})
2
3
4
# 路由独享守卫
beforeEnter: (to, from) => {...} 在单个路由配置中定义,仅对该路由生效。
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// 只有这个路由会执行
if (!isAdmin) {
return '/forbidden'
}
}
}
]
2
3
4
5
6
7
8
9
10
11
12
# 组件内守卫
beforeRouteEnter(to, from) 在组件中定义。
会在组件创建前调用。
import { onbeforeRouteEnter } from 'vue-router'
onbeforeRouteEnter((to, from) => {...})
2
3
beforeRouteUpdate(to, from) 在组件中定义。
当前路由改变但组件复用时调用。
import { onbeforeRouteUpdate } from 'vue-router'
onbeforeRouteUpdate((to, from) => {...})
2
3
beforeRouteLeave(to, from) 在组件中定义。
离开当前路由前调用,可取消导航。
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {...})
2
3
# 示例
例如判断用户是否登录。
// router/index.js
router.beforeEach((to, from) => {
// 排除登录和注册页面
const publicPages = ['/login', '/register']
const authRequired = !publicPages.includes(to.path)
// 获取登录Token
const isLoggedIn = localStorage.getItem('token')
if (authRequired && !isLoggedIn) {
return '/login'
}
if (!authRequired && isLoggedIn) {
// 已登录用户访问登录页,重定向到首页
return '/'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16