第 1 节 概述
到目前为止,我们一直在单页面上做文章。
但真实的应用有多个页面:首页、列表页、详情页、个人中心……
用户点击导航栏,URL 变化,页面内容切换——这就是路由要做的事。
Vue 3 的官方路由库是 Vue Router。它的核心思路很简单:URL 映射到组件。
1
2
3
| /home → Home.vue
/news → News.vue
/about → About.vue
|
第 2 节 安装与配置
如果使用 npm create vue@latest 创建项目时勾选了 Router,它会自动配置好。
手动安装:
然后创建路由配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
const routes = [
{ path: '/home', component: Home },
{ path: '/news', component: News },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
|
在 main.ts 中注册:
1
2
3
4
5
6
7
| import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
|
第 3 节 页面跳转
3.1 RouterLink 和 RouterView
Vue Router 提供了两个全局组件:
RouterLink:替代 <a> 标签,点击时不刷新页面RouterView:当前路由对应的组件渲染在这个位置
1
2
3
4
5
6
7
8
9
10
11
12
| <!-- App.vue -->
<template>
<nav>
<router-link to="/home">首页</router-link>
<router-link to="/news">新闻</router-link>
<router-link to="/about">关于</router-link>
</nav>
<main>
<router-view />
</main>
</template>
|
点击 router-link 时,Vue Router 会拦截点击,更新 URL,并把对应的组件渲染到 router-view 中。
说明
router-link 和 router-view 在模板中也可以写成 kebab-case:<router-link> 和 <router-view>。
3.2 路由配置
在 /src/pages/ 下创建三个页面组件:
1
2
3
4
5
| <!-- Home.vue -->
<template>
<h1>首页</h1>
<p>欢迎来到首页</p>
</template>
|
1
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
31
| // src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
const routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
component: Home,
},
{
path: '/news',
component: News,
},
{
path: '/about',
component: About,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
|
redirect 表示访问 / 时自动跳转到 /home。
第 4 节 命名路由
路由可以添加 name 属性,用名称代替路径:
1
2
3
4
5
6
7
| const routes = [
{
path: '/home',
name: 'home',
component: Home,
},
]
|
模板中使用:
1
| <router-link :to="{ name: 'home' }">首页</router-link>
|
用名称的好处:如果路径变了,只需改一处(路由配置),不需要改所有模板。
第 5 节 动态路由匹配
5.1 路由参数
新闻详情页的 URL 可能是 /news/1、/news/2,其中 1 和 2 是动态变化的。
用 :id 来匹配动态段:
1
2
3
4
5
6
7
| const routes = [
{
path: '/news/:id',
name: 'news-detail',
component: NewsDetail,
},
]
|
在组件中获取参数:
1
2
3
4
5
6
7
8
9
10
11
| <!-- NewsDetail.vue -->
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const id = route.params.id
</script>
<template>
<p>新闻 ID:{{ id }}</p>
</template>
|
跳转时传入参数:
1
2
3
| <router-link :to="{ name: 'news-detail', params: { id: 1 } }">
新闻1
</router-link>
|
5.2 多个参数
可以有多个动态段:
1
| { path: '/user/:userId/post/:postId', component: UserPost }
|
5.3 参数变化时组件复用
同一个路由组件,参数变化时组件会被复用(不会销毁重建)。
如果需要在参数变化时重新获取数据,可以使用 watch 监听 route.params:
1
2
3
4
5
6
7
8
9
10
11
12
| import { useRoute } from 'vue-router'
import { watch } from 'vue'
const route = useRoute()
watch(
() => route.params.id,
(newId) => {
// 根据新的 ID 重新获取数据
fetchArticle(newId)
}
)
|
第 6 节 嵌套路由
新闻页面下,左侧是列表,右侧是详情区域。这时需要嵌套路由。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import News from '@/pages/News.vue'
import NewsDetail from '@/pages/NewsDetail.vue'
const routes = [
{
path: '/news',
component: News,
children: [
{
path: ':id', // 实际路径:/news/1
name: 'news-detail',
component: NewsDetail,
},
],
},
]
|
在 News.vue 中需要一个 router-view 来渲染子路由:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <!-- News.vue -->
<template>
<div class="flex">
<div class="sidebar">
<router-link
v-for="item in newsList"
:key="item.id"
:to="{ name: 'news-detail', params: { id: item.id } }"
>
{{ item.title }}
</router-link>
</div>
<div class="content">
<router-view />
</div>
</div>
</template>
|
子路由的内容会渲染在父组件的 router-view 位置。
第 7 节 路由传参
7.1 params 传参
参数作为 URL 路径的一部分:
1
2
| /news/1 → params: { id: 1 }
/news/1/张三 → params: { id: 1, author: '张三' }
|
1
2
3
| <router-link :to="{ name: 'news-detail', params: { id: 1 } }">
新闻1
</router-link>
|
7.2 query 传参
参数作为 URL 查询字符串:
1
| /news?id=1&page=2 → query: { id: '1', page: '2' }
|
1
2
3
| <router-link :to="{ name: 'news', query: { id: 1 } }">
新闻1
</router-link>
|
7.3 两种方式的对比
| 对比 | params | query |
|---|
| URL 格式 | /news/1 | /news?id=1 |
| 参数位置 | 路径部分 | 查询字符串 |
| 适用场景 | 必填参数、资源标识 | 可选参数、过滤条件 |
| 刷新是否丢失 | 不丢失 | 不丢失 |
第 8 节 编程式导航
除了用 router-link,还可以在 JavaScript 中控制跳转。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { useRouter } from 'vue-router'
const router = useRouter()
// 跳转到指定路径
router.push('/home')
// 按名称跳转
router.push({ name: 'news-detail', params: { id: 1 } })
// 替换当前历史记录(不能回退)
router.replace('/home')
// 前进/后退
router.go(1) // 前进一页
router.go(-1) // 后退一页
|
push 和 replace 的区别:
- push:添加一条新历史记录,可以点"返回"
- replace:替换当前历史记录,不能点"返回"回到上一个页面
第 9 节 导航守卫
9.1 全局前置守卫
导航触发时,可以用 beforeEach 做拦截:
1
2
3
4
5
6
7
8
9
10
11
12
| // src/router/index.ts
import { createRouter } from 'vue-router'
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// 检查目标路由是否需要登录
if (to.meta.requiresAuth && !isLoggedIn()) {
// 未登录,重定向到登录页
return { name: 'login' }
}
})
|
9.2 路由元信息
可以用 meta 给路由附加信息:
1
2
3
4
5
6
7
8
9
10
| const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: '管理后台',
},
},
]
|
在守卫中读取:
1
2
3
| router.beforeEach((to) => {
document.title = to.meta.title as string || '默认标题'
})
|
9.3 完整的守卫流程
1
2
3
4
5
| 导航触发
→ beforeEach(全局)
→ beforeEnter(路由独享)
→ 组件内守卫
→ afterEach(全局,导航确认后)
|
afterEach 不能中断导航,适合做日志记录、页面统计:
1
2
3
| router.afterEach((to) => {
console.log(`导航到:${to.path}`)
})
|
第 10 节 404 页面
用 :pathMatch(.*)* 捕获所有未匹配的路径:
1
2
3
4
5
6
7
8
| const routes = [
// ...其他路由
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: NotFound,
},
]
|
这个路由要放在最后,这样就先匹配已有路由,都不匹配才走 404。
路由解决了"页面切换"的问题。现在应用有多个页面了。
但还有一个问题:多个页面需要共享同一份数据。
比如用户登录后,用户信息要在首页、个人中心、设置页等多处使用。每个页面都去调接口太浪费,存在哪里统一管理?
答案是 Pinia,Vue 3 的官方状态管理库。