Vue Router 详解
Vue Router 是 Vue.js 官方的路由管理器。它与 Vue.js 深度集成,用于构建单页应用(SPA)。通过 Vue Router,开发者可以轻松地将组件与 URL 路径映射起来,实现无刷新页面切换、参数传递、导航守卫等功能,从而为用户提供流畅的导航体验。
核心思想:
- 组件化路由:Vue 路由的配置与 Vue 组件紧密结合,将不同的 URL 路径映射到相应的组件。
- 声明式导航:通过
<router-link>组件实现导航,而非传统<a>标签引起页面刷新。 - 编程式导航:提供
router.push()、router.replace()等方法,通过 JavaScript 控制路由跳转。 - 导航守卫:提供全局、路由独享和组件内的生命周期钩子,用于在导航前后执行逻辑(如权限验证、数据预加载)。
- 两种路由模式:支持 Hash 模式和 History 模式,满足不同部署需求。
一、为什么需要 Vue Router?
在传统的 Web 应用中,每次页面跳转都会向服务器发送请求,导致整个页面重新加载。这在用户体验和性能上都存在瓶颈。单页应用 (Single Page Application, SPA) 旨在解决这些问题,通过在浏览器端仅加载一次 HTML、CSS 和 JavaScript 资源,然后通过 JavaScript 动态地更新页面内容,模拟多页应用的用户体验。
要实现 SPA,核心在于客户端路由。它负责:
- URL 与组件的映射:根据 URL 路径显示不同的页面组件。
- 管理浏览器历史记录:在不刷新页面的情况下,改变 URL 并使用浏览器的前进/后退功能。
- 参数传递:从 URL 或通过编程方式向组件传递数据。
- 导航控制:在路由跳转前后进行权限验证、数据预加载等操作。
Vue Router 正是为 Vue.js 应用提供这些功能的官方解决方案。它与 Vue.js 的响应式系统、组件系统深度结合,让构建复杂的 SPA 变得简单而高效。
二、安装与基本使用
2.1 安装 Vue Router
在你的 Vue 项目中,使用 npm 或 yarn 进行安装:
1 | npm install vue-router@next # 对于 Vue 3 |
本文主要以 Vue Router 4 (对应 Vue 3) 为例进行讲解。
2.2 基本配置与使用
创建路由实例:
首先,创建一个
router实例,并定义你的路由规则。通常在一个单独的文件(如src/router/index.js)中进行配置。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
32// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// 懒加载模式,访问该路由时才加载组件,优化性能
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
path: '/user/:id', // 动态路由匹配,:id 是一个路由参数
name: 'user',
component: () => import('../views/UserView.vue'),
props: true // 将路由参数作为 props 传递给组件
}
]
const router = createRouter({
history: createWebHistory(), // 使用 History 模式 (推荐用于生产环境)
// history: createWebHashHistory(), // 或者使用 Hash 模式
routes
})
export default router在 Vue 应用中注册路由:
将创建的
router实例注册到你的 Vue 应用中。1
2
3
4
5
6// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 引入路由实例
createApp(App).use(router).mount('#app')在组件中使用
<router-view>和<router-link>:<router-view>:用于渲染匹配到的路由组件。它类似于一个占位符,不同的路由路径会渲染不同的组件到这个位置。<router-link>:用于在应用中进行导航。它会被渲染为<a>标签,但会阻止其默认行为,通过 Vue Router 进行导航,避免页面刷新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<!-- src/App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/user/123">User 123</router-link>
</nav>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
/* 可以在这里添加一些样式 */
</style>1
2
3
4
5
6
7
8
9
10
11
12
13<!-- src/views/HomeView.vue -->
<template>
<div>
<h1>Home Page</h1>
<p>Welcome to the home page!</p>
</div>
</template>
<script>
export default {
name: 'HomeView'
}
</script>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<!-- src/views/UserView.vue -->
<template>
<div>
<h1>User Profile</h1>
<p>User ID: {{ userId }}</p>
</div>
</template>
<script>
export default {
name: 'UserView',
props: ['id'], // 接收来自路由参数的 prop 'id'
computed: {
userId() {
return this.id || this.$route.params.id; // prefer prop, fallback to $route.params
}
}
// 或者在 setup 中使用 useRoute
// setup(props) {
// const route = useRoute();
// const userId = computed(() => props.id || route.params.id);
// return { userId };
// }
}
</script>
三、路由模式
Vue Router 提供了两种基本的路由模式:Hash 模式和 History 模式。
3.1 Hash 模式 (createWebHashHistory())
- 工作原理:利用 URL 中的
hash(形如#/path/to/page) 部分来模拟完整的 URL 路径。当 hash 值改变时,浏览器不会重新加载页面,而是触发hashchange事件。 - 优点:
- 无需服务器配置:所有页面都基于
#后的路径进行切换,服务器始终只返回index.html。 - 兼容性好:适用于所有浏览器,包括旧版浏览器。
- 无需服务器配置:所有页面都基于
- 缺点:
- URL 不美观:带有
#符号的 URL 在用户看来不够友好。 - SEO 不佳:搜索引擎通常不会索引
hash部分的内容。
- URL 不美观:带有
3.2 History 模式 (createWebHistory())
- 工作原理:利用 HTML5 History API 的
pushState()和replaceState()方法来改变 URL,而无需重新加载页面。这使得 URL 看起来像传统的服务器路由路径,没有#符号。 - 优点:
- URL 美观:与传统 Web 应用的 URL 完全一致。
- SEO 友好:搜索引擎可以更好地索引这些 URL。
- 缺点:
- 需要服务器配置:当用户直接访问一个 History 模式的 URL(例如
example.com/about)或刷新页面时,浏览器会向服务器发出请求。如果服务器不知道如何处理这个 URL,就会返回 404 错误。因此,服务器需要配置一个“回退路由”,将所有非静态资源请求都重定向到应用的index.html。- Nginx 示例配置:
1
2
3
4
5
6
7
8
9
10
11server {
listen 80;
server_name yourdomain.com;
root /path/to/your/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
} - Apache 示例配置:
在项目的public(或dist) 目录下创建.htaccess文件:1
2
3
4
5
6
7
8<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ -
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html
</IfModule>
- Nginx 示例配置:
- 需要服务器配置:当用户直接访问一个 History 模式的 URL(例如
四、动态路由匹配与参数传递
4.1 动态路由 (path: '/user/:id')
在路由配置中,可以使用冒号 : 来定义动态片段。例如 /user/:id 会匹配 /user/123、/user/abc 等。:id 就是一个路由参数。
4.2 访问路由参数
**
$route.params**:在任何组件中,可以通过this.$route.params(在选项式 API 中) 或route.params(在组合式 API 中,通过useRoute()获取route对象) 来访问当前路由的所有参数。1
2
3
4
5
6// 在UserView.vue中
export default {
created() {
console.log(this.$route.params.id); // 访问 :id 参数
}
}props选项:在路由配置中设置props: true,可以将路由参数作为组件的 props 传递。这有助于组件与路由解耦,使组件更通用。1
2
3
4
5
6
7
8
9
10
11
12
13// router/index.js
{
path: '/user/:id',
component: UserView,
props: true // 将 :id 作为 prop 传递给 UserView
}
// UserView.vue
<script>
export default {
props: ['id'] // 在组件中声明接收 id prop
}
</script>props也可以是一个对象(静态值)、一个函数(动态返回值)或者一个包含$route属性的布尔值(true)。
五、嵌套路由
通过嵌套路由可以构建更复杂的 UI 结构。一个 router-view 内部还可以包含另一个 router-view。
1 | // router/index.js |
1 | <!-- UserView.vue --> |
六、命名路由与命名视图
6.1 命名路由
通过给路由配置 name 属性,可以为路由定义一个名称。这使得在导航时可以通过名称引用路由,而不是硬编码 URL 路径,使得路由跳转更具可维护性。
1 | // router/index.js (片段) |
1 | <!-- 导航到命名路由 --> |
6.2 命名视图
有时,你可能需要同时显示多个视图,而不是嵌套显示。例如,一个布局可能有一个侧边栏和一个主内容区域,并且这两个区域都由路由控制。这时可以使用命名视图。
1 | // router/index.js (片段) |
1 | <!-- App.vue (片段) --> |
七、编程式导航
除了 <router-link> 进行声明式导航,Vue Router 也提供了编程式导航方法,通过 router 实例来控制路由跳转。
在 Vue 3 中,可以通过 useRouter() 组合式函数获取 router 实例。
1 | import { useRouter } from 'vue-router'; |
router.push(location):向历史记录堆栈添加新的路由条目。location可以是字符串路径 ('/home') 或包含path/name/params/query的对象。
router.replace(location):替换当前历史记录条目,不会添加新条目。router.go(n):在历史记录中前进或后退n步。router.back():等价于router.go(-1)。router.forward():等价于router.go(1)。
八、导航守卫 (Navigation Guards)
导航守卫是 Vue Router 提供的用于在路由导航过程中进行拦截、处理和控制的钩子。它们常用于权限验证、登录状态检查、数据预加载等场景。
导航守卫的执行顺序如下:
- 全局前置守卫:
router.beforeEach() - 路由独享的守卫:
beforeEnter(在路由配置中定义) - 组件内守卫:
beforeRouteEnter(在组件创建前)beforeRouteUpdate(路由参数变化,组件复用时)beforeRouteLeave(离开当前组件前)
- 全局解析守卫:
router.beforeResolve() - 全局后置守卫:
router.afterEach()
所有这些守卫函数都接收三个参数:
to:目标路由对象,即将进入的路由信息。from:当前路由对象,即将离开的路由信息。next:一个函数,必须调用它才能继续导航。next(): 放行。next(false): 中断当前导航。next('/')或next({ path: '/' }): 跳转到一个新的地址。next(error): 导航失败,且错误被传递到router.onError()侦听器。
8.1 全局前置守卫 (router.beforeEach)
在每次导航开始时触发。
1 | // router/index.js |
8.2 路由独享的守卫 (beforeEnter)
在路由配置中直接定义,只对该路由生效。
1 | // router/index.js |
8.3 组件内守卫
定义在 Vue 组件内部。
beforeRouteEnter(to, from, next):在路由进入组件之前被调用。注意:在此时,组件实例还没有被创建,所以不能通过this访问组件实例。如果需要访问组件实例,可以将回调函数作为参数传递给next()。1
2
3
4
5
6
7
8
9
10
11
12
13<!-- MyComponent.vue -->
<template>...</template>
<script>
export default {
beforeRouteEnter(to, from, next) {
console.log('进入组件之前');
next(vm => {
// 通过 vm 访问组件实例
console.log('组件实例已创建,可以访问 this:', vm);
});
}
}
</script>beforeRouteUpdate(to, from, next):在当前路由改变,但该组件被复用时调用(例如,从/user/1跳转到/user/2,同一个UserView组件会被复用)。可以访问this。1
2
3
4
5
6
7
8
9
10
11
12<!-- MyComponent.vue -->
<template>...</template>
<script>
export default {
beforeRouteUpdate(to, from, next) {
console.log('路由更新,组件复用', this.$route.params.id, '->', to.params.id);
// 通常在这里重新加载数据
this.fetchData(to.params.id);
next();
}
}
</script>beforeRouteLeave(to, from, next):在离开当前组件之前调用。可以访问this。常用于提示用户“未保存的更改”。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- MyComponent.vue -->
<template>...</template>
<script>
export default {
data() { return { hasUnsavedChanges: true } },
beforeRouteLeave(to, from, next) {
console.log('离开组件之前');
if (this.hasUnsavedChanges && !confirm('您有未保存的更改,确定要离开吗?')) {
next(false); // 阻止离开
} else {
next(); // 允许离开
}
}
}
</script>
8.4 全局解析守卫 (router.beforeResolve)
在所有组件内守卫和异步路由组件被解析之后,但是路由最终确认之前被调用。这个守卫可以用来确保在导航即将完成时,所有异步操作都已完成。
1 | router.beforeResolve(async (to, from, next) => { |
8.5 全局后置守卫 (router.afterEach)
在导航被确认之后、组件渲染之前被调用。这些守卫不接受 next 函数,也不能改变导航。它们主要用于打印日志、分析报告或修改页面标题等非阻塞操作。
1 | router.afterEach((to, from) => { |
九、路由元信息 (Meta Fields)
你可以在路由配置中定义 meta 字段,存储任意信息。这在导航守卫中非常有用,例如用来标记哪些路由需要权限验证。
1 | // router/index.js |
在导航守卫中访问:
1 | router.beforeEach((to, from, next) => { |
十、数据获取
在 SPA 中,数据获取可以是异步的。Vue Router 提供了几种策略:
- 在组件的
created/mounted钩子中获取:
这是最常见的方式。当组件被渲染时才发起数据请求。优点是简单,缺点是用户可能先看到空白或旧数据。 - 在导航守卫中获取数据:
在beforeRouteEnter或beforeResolve等守卫中预加载数据。优点是可以避免用户看到空白页面,只有数据加载完成后才进入路由。缺点是如果数据加载缓慢,用户会感到导航延迟。 - 同时进行:
组件自身加载数据,并显示加载状态。导航守卫中不阻塞,但可能进行一些参数检查等。
十一、总结
Vue Router 是 Vue.js 生态系统中不可或缺的一部分,它为构建复杂、高性能的单页应用提供了强大而灵活的客户端路由解决方案。
通过使用 Vue Router,你可以:
- 声明式地将 URL 映射到 Vue 组件,实现组件级别的路由。
- 管理浏览器的历史记录,提供类似多页应用的导航体验。
- 通过动态路由和
props选项方便地传递数据到组件。 - 利用导航守卫进行精细的权限控制和数据预加载,提升用户体验和应用安全性。
- 选择 Hash 或 History 模式,以适应不同的部署环境和 SEO 需求。
熟练掌握 Vue Router 对于 Vue.js 开发者来说至关重要,它能帮助你构建结构清晰、功能强大且用户体验优秀的单页应用程序。
