chapter 8(路由)
RouteRecordRaw(Vue Router 路由配置对象)
1. 核心概念
在 Vue Router 中,路由(Route) 是通过 RouteRecordRaw
接口定义的配置对象,描述了一个 URL 路径(path
)与对应组件(component
)的映射关系。它是 Vue Router 的路由配置基础。
2. 核心属性
一个典型的 RouteRecordRaw
对象包含以下关键属性:
属性名 | 类型 | 说明 |
---|---|---|
path | string | 路径模式(如 '/about' , '/user/:id' ),匹配 URL 的路径部分。 |
component | Component | 匹配路径时渲染的组件(单个组件或组件对象)。 |
name | string | 路由名称(可选),用于通过 router-link 或 router.push 精确跳转。 |
children | RouteRecordRaw[] | 嵌套路由配置,将子路由挂载到当前路由的 <router-view> 中。 |
redirect | string | Function` |
alias | string | Array` |
meta | any | 自定义元数据(如权限标记、页面标题),可在守卫中访问。 |
beforeEnter | NavigationGuard | 路由独享守卫,仅对当前路由生效。 |
3. 基础示例
import { type RouteRecordRaw } from "vue-router"
const routes: RouteRecordRaw[] = [
{
path: '/about',
component: () => import('@/views/About.vue'),
name: 'AboutPage',
meta: { requiresAuth: false },
},
{
path: '/user/:id',
component: UserView,
children: [
{ path: 'profile', component: Profile },
{ path: 'settings', component: Settings },
],
},
];
4. 高级配置
4.1 动态路由参数
{
path: '/user/:id',
component: UserView,
}
// URL 示例:/user/123 → params.id = '123'
4.2 路由重定向
{
path: '/home',
redirect: '/dashboard', // 跳转到 '/dashboard'
},
{
path: '/old-path',
redirect: to => {
return '/new-path';
},
}
4.3 路由别名
{
path: '/dashboard',
component: Dashboard,
alias: '/dash', // '/dash' 和 '/dashboard' 映射到同一组件
},
4.4 嵌套路由
{
path: '/settings',
component: SettingsLayout,
children: [
{ path: 'profile', component: ProfileSettings },
{ path: 'security', component: SecuritySettings },
],
}
// URL: /settings/profile → 渲染 SettingsLayout 和 ProfileSettings
4.5 路由元数据与守卫
{
path: '/admin',
component: AdminDashboard,
meta: { requiresAuth: true }, // 自定义权限标记
beforeEnter: (to, from, next) => {
if (!userIsLoggedIn) next('/login');
else next();
},
}
5. 路由配置的完整流程
- 定义路由数组:用
RouteRecordRaw
对象描述所有路由。 - 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes, // 路由配置数组
});
- 挂载到 Vue 应用
createApp(App).use(router).mount('#app');
6. 注意事项
- 组件导入方式
- 直接导入:
import Component from './Component.vue'
(适合小应用)。 - 懒加载:
() => import('./Component.vue')
(优化初始加载性能)。 - 路径匹配规则
- 以
^
开头和$
结尾,确保精确匹配。 - 动态参数需以
:
开头(如:id
)。 - 404 页面
{
path: '/:pathMatch(.*)*', // 匹配所有未定义的路径
component: NotFound,
}
7. 典型场景
- 动态路由:根据用户角色动态生成路由(如
/admin/*
)。 - 权限控制:通过
meta.requiresAuth
和全局守卫控制访问。 - 多级导航:嵌套路由实现复杂页面结构(如仪表盘 → 子模块)。
总结
- RouteRecordRaw 是 Vue Router 路由配置的核心接口,定义了路径与组件的映射关系。
- 通过
path
,component
,children
等属性实现路由的灵活配置。 - 结合
meta
,redirect
,beforeEnter
等高级属性,可实现权限控制、动态跳转等复杂逻辑。
创建 Router 实例
使用 createRouter
和 createWebHistory
方法创建路由实例:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 继续 src/router/index.ts
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), // HTML5 History 模式
routes, // 路由配置数组
});
- history:选择路由模式(
createWebHistory
为 HTML5 History 模式,createWebHashHistory
为 hash 模式)。 - routes:传递之前定义的路由配置数组。
Vite公开了环境对象Meta,其中包含一个BASE_URL属性。您可以在专用环境文件中设置BASE_URL的值,通常以.env
前缀表示,或者在运行Vite服务器时通过命令行设置。然后Vite提取BASE_URL的相关值并将其注入到import.Meta.env对象中,我们可以在代码中使用它.
您不必在.env
文件中为开发设置BASE_URL值。Vite会自动将其映射到本地服务器URL。大多数现代托管平台(如Netlify)会在部署过程中为您设置BASE_URL
值,通常为应用程序的域名。
完整代码示例
src/router/index.ts:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: Home,
name: 'Home',
meta: { title: '首页' },
},
{
path: '/about',
component: About,
name: 'About',
},
{
path: '/user/:id',
component: () => import('@/views/User.vue'),
name: 'User',
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
export default router;
挂载到 Vue 应用
在主文件中引入并挂载路由实例,在初始化应用程序实例app的main.ts文件中,我们将导入创建的路由器实例,并将其作为参数传递给app.use()
方法:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router); // 挂载路由
app.mount('#app');
<RouterView>
和 <RouterLink>
1. <RouterView>
:路由出口组件
<RouterView>
是 Vue Router 的核心组件,用于渲染匹配到的路由组件。每个 <RouterView>
对应一个路由层级,支持嵌套路由和多视图。
核心用法
<!-- App.vue -->
<template>
<div id="app">
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</nav>
<RouterView /> <!-- 渲染匹配的路由组件 -->
</div>
</template>
关键特性
特性 | 说明 |
---|---|
嵌套路由 | 支持多层 <RouterView> 渲染子路由组件(如仪表盘的子页面)。 |
命名视图 | 通过 name 属性同时渲染多个视图(需路由配置 components )。 |
嵌套路由示例
<!-- 父路由组件 Parent.vue -->
<template>
<div>
<h2>父组件内容</h2>
<RouterView /> <!-- 渲染子路由组件 -->
</div>
</template>
// 路由配置
const routes = [
{
path: '/parent',
component: Parent,
children: [
{ path: 'child1', component: Child1 },
{ path: 'child2', component: Child2 },
],
},
];
命名视图示例
<!-- App.vue -->
<template>
<RouterView /> <!-- 默认视图 -->
<RouterView name="sidebar" /> <!-- 命名视图 "sidebar" -->
</template>
// 路由配置
{
path: '/home',
components: {
default: HomeMain,
sidebar: HomeSidebar,
},
},
2. <RouterLink>
:声明式导航组件
<RouterLink>
是 Vue Router 提供的声明式导航组件,替代原生 <a>
标签实现无页面刷新跳转。
基础用法
<RouterLink to="/about">关于</RouterLink>
关键属性
属性名 | 类型 | 说明 |
---|---|---|
to | string /Object | 目标路由路径(如 /about )或对象(如 { name: 'User', params: { id: '123' } } )。 |
replace | boolean | 是否替换历史记录(类似 router.replace() )。 |
active-class | string | 指定激活链接的类名(默认 router-link-active )。 |
exact | boolean | 是否精确匹配路径(如 /about 匹配 /about ,但不匹配 /about/me )。 |
动态跳转示例
<RouterLink :to="{ name: 'User', params: { id: userId } }">用户详情</RouterLink>
编程式导航替代
<template>
<button @click="goToAbout">跳转到关于页</button>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const goToAbout = () => {
router.push('/about');
};
</script>
router-link-active
和 router-link-exact-active
这两个 CSS 类由 <RouterLink>
自动添加,用于高亮当前激活的链接。它们的区别在于匹配路径的严格程度:
类名 | 说明 |
---|---|
router-link-active | 当链接路径与当前路由部分匹配时生效(如 /user/123 匹配 /user )。 |
router-link-exact-active | 当链接路径与当前路由完全匹配时生效(如 /user 匹配 /user ,但不匹配 /user/123 )。 |
使用 custom 属性和 v-slot
默认情况下,<RouterLink>
渲染为 <a>
标签。若需将其渲染为其他元素(如 <button>
),可通过以下方法实现:
<template>
<!-- 自定义为按钮 -->
<RouterLink
:to="{ name: 'About' }"
custom
v-slot="{ navigate, href, isExactActive }"
>
<button
:class="{ 'active': isExactActive }"
:href="href"
@click="navigate"
>
关于页面
</button>
</RouterLink>
</template>
- custom + v-slot 是自定义
<RouterLink>
渲染的核心方法。 - 通过插槽暴露的
navigate
函数实现路由跳转,结合isExactActive
等属性控制样式。 - 适用于按钮、自定义图标、SVG 等非
<a>
元素的场景,同时保持路由功能。
通过此方法,可完全控制 <RouterLink>
的渲染形式,同时保留 Vue Router 的导航逻辑和激活状态管理。
路由间传递数据
在 Vue Router 中,在路由之间传递数据可以通过多种方式实现,具体选择取决于数据的类型和需求。以下是常见的方法及示例:
1. 通过 params(路径参数)
适用场景:传递动态路径参数(如用户 ID、文章 ID 等)。
步骤:
- 定义路由路径:在路由配置中使用
:paramName
定义动态参数。 - 跳转时传递参数:通过
params
传递数据。 - 接收参数:在目标组件中通过
useRoute()
或this.$route
获取。
示例:
// 路由配置(router/index.ts)
const routes = [
{
path: '/user/:id',
component: UserDetail,
name: 'UserDetail',
},
];
// 跳转(使用编程式导航)
router.push({
name: 'UserDetail',
params: { id: '123' }, // 传递参数
});
// 接收参数(UserDetail.vue)
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const userId = route.params.id as string; // 获取参数
</script>
2. 通过 query(查询参数)
适用场景:传递非路径相关的数据(如搜索条件、分页参数等)。
步骤:
- 跳转时传递查询参数:通过
query
对象传递。 - 接收参数:在目标组件中通过
route.query
获取。
示例:
// 跳转(编程式导航)
router.push({
path: '/search',
query: { q: 'vue', page: '1' }, // 传递查询参数
});
// 接收参数(Search.vue)
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const searchQuery = route.query.q as string; // 'vue'
const pageNumber = Number(route.query.page); // 1
</script>
3. 通过 props(路由配置中声明 props)
适用场景:将路由参数或查询参数直接传递给目标组件作为 props
。
步骤:
- 在路由配置中启用
props
:
props: true
:将所有params
作为 props 传递。props: (route) => ({ ... })
:自定义传递的 props。
示例:
// 路由配置
{
path: '/user/:id',
component: UserDetail,
props: true, // 启用 params → props
},
// 目标组件(UserDetail.vue)
<script setup>
const props = defineProps<{
id: string;
}>();
</script>
4. 通过路由的 meta 字段
适用场景:传递元数据(如权限标记、标题等),不通过 URL 传递。
步骤:
- 在路由配置中定义 meta:
- 在守卫或组件中读取:通过
route.meta
获取。
示例:
// 路由配置
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true }, // 自定义元数据
},
// 在守卫中使用(全局守卫)
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 验证权限
}
next();
});
5. 通过状态管理(如 Pinia/Vuex)
适用场景:需要跨组件共享复杂数据(如用户信息、表单数据等)。
步骤:
- 存储数据到状态管理:在跳转前将数据存入 store。
- 在目标组件中读取:直接从 store 中获取。
示例(Pinia):
// 存储数据(在跳转前)
const userStore = useUserStore();
userStore.setUserData({ id: 123, name: 'Alice' });
// 跳转
router.push('/user/profile');
// 目标组件中读取
<script setup>
const userStore = useUserStore();
const userData = userStore.userData;
</script>
6. 通过路由守卫传递数据
适用场景:在导航过程中预加载数据并传递给目标组件。
步骤:
- 在守卫中获取数据:在
beforeEach
或beforeResolve
中获取数据。 - 将数据挂载到路由对象:通过
route.meta
或其他方式传递。
示例:
// 全局守卫
router.beforeEach(async (to, from, next) => {
if (to.params.id) {
const data = await fetchData(to.params.id as string);
to.meta.user = data; // 将数据挂载到 route.meta
}
next();
});
// 目标组件中读取
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const userData = route.meta.user; // 获取守卫中传递的数据
</script>
选择方法的建议
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
params/query | 需要通过 URL 传递数据(如路径参数或过滤条件) | 简单直接,支持浏览器历史记录和书签。 | 数据暴露在 URL 中,可能不适合敏感信息。 |
props | 需要将路由参数直接映射到组件 props | 简化组件数据传递。 | 仅适用于简单数据,需在路由配置中声明。 |
meta 字段 | 传递元数据(如权限、标题) | 轻量且直接。 | 仅限于路由配置中定义的数据,无法动态更新。 |
状态管理(Pinia) | 需要跨组件共享复杂数据或长期存储 | 可维护性强,支持复杂逻辑。 | 需要额外配置 store。 |
路由守卫 | 需要在导航过程中预加载数据 | 灵活控制数据加载流程。 | 代码分散,可能增加复杂度。 |
注意事项
- 敏感数据:避免通过
params
或query
传递敏感信息(如密码、令牌)。 - 类型安全:在 TypeScript 中使用
route.params
时,需显式类型断言(如as string
)。 - 性能优化:对于大量数据,优先使用状态管理而非 URL 参数。
通过以上方法,可以灵活地在 Vue Router 的路由间传递数据,选择合适的方式取决于具体需求和场景。
路由守卫
1. router.beforeEach
作用:在导航被确认之前(但在异步路由组件加载之前)触发,是最常用的守卫,用于全局拦截导航。
核心参数
- to:目标路由对象(即将跳转到的路由)。
- from:当前路由对象(当前所在的路由)。
next
:必须调用此函数以继续导航:next()
:继续导航。next(false)
:取消导航。next('/path')
:重定向到其他路径。
典型用例
- 权限验证:检查用户是否登录。
- 设置页面标题:根据路由
meta
设置标题。 - 加载状态控制:显示加载动画。
示例代码
router.beforeEach(async (to, from, next) => {
// 1. 检查登录状态
const isAuthenticated = await checkAuth();
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login');
return;
}
// 2. 设置页面标题
document.title = to.meta.title || '默认标题';
// 3. 允许导航
next();
});
2. router.beforeResolve
作用:在导航被确认且所有异步路由组件加载完成后触发,但在实际渲染之前。
适用场景:需要在路由组件完全加载后执行操作(如数据预加载)。
典型用例
- 数据预加载:确保异步组件数据加载完成后再渲染。
- 依赖注入:在渲染前注入全局数据。
示例代码
router.beforeResolve((to, from, next) => {
// 确保异步组件已加载
if (to.meta.loadData) {
// 预加载数据
loadDataForRoute(to).then(() => {
next();
});
} else {
next();
}
});
3. router.afterEach
作用:在导航完成(即路由已更新,组件已渲染)后触发。
适用场景:导航后的清理操作、埋点统计、滚动位置恢复等。
典型用例
- 滚动位置恢复:跳转后回到顶部或指定位置。
- 埋点统计:记录页面访问数据。
- 资源释放:清理临时数据。
示例代码
router.afterEach((to, from) => {
// 1. 滚动到顶部
window.scrollTo(0, 0);
// 2. 埋点统计(如 Google Analytics)
ga('send', 'pageview', to.fullPath);
// 无需调用 next()
});
关键区别对比
守卫 | 触发时机 | 是否需要 next() | 典型用途 |
---|---|---|---|
beforeEach | 导航确认前(异步组件加载前) | 必须 | 权限验证、标题设置、加载状态控制 |
beforeResolve | 导航确认后,所有异步组件加载完成但渲染前 | 可选 | 数据预加载、依赖注入 |
afterEach | 导航完成(路由已更新,组件已渲染) | 无需 | 滚动位置恢复、埋点统计、资源清理 |
常见问题与解决方案
问题 1:如何避免无限循环?
- 场景:在
beforeEach
中调用next('/login')
,但/login
路由又触发了相同的守卫。 - 解决 :添加条件判断:
if (to.path === '/login') {
next();
return;
}
问题 2:异步操作如何处理?
- 示例 :在
beforeEach
中进行异步登录检查:
router.beforeEach(async (to, from, next) => {
const isAuthenticated = await checkAuth();
if (!isAuthenticated) {
next('/login');
} else {
next();
}
});
问题 3:如何动态设置页面标题?
- 方法 :在路由的
meta
字段定义标题,守卫中读取:
typescript
// 路由配置
{
path: '/about',
component: About,
meta: { title: '关于我们' },
}
// beforeEach 中
document.title = to.meta.title || '默认标题';
完整示例代码
// src/router/index.ts
import { createRouter, createWebHistory, Router } from 'vue-router';
import { checkAuth } from '@/utils/auth';
const router: Router = createRouter({
history: createWebHistory(),
routes: [...],
});
// 1. beforeEach: 权限验证和标题设置
router.beforeEach(async (to, from, next) => {
const isAuthenticated = await checkAuth();
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login');
return;
}
document.title = to.meta.title || '默认标题';
next();
});
// 2. beforeResolve: 预加载数据
router.beforeResolve((to, from, next) => {
if (to.meta.loadData) {
loadDataForRoute(to).then(() => {
next();
});
} else {
next();
}
});
// 3. afterEach: 滚动到顶部和埋点
router.afterEach((to, from) => {
window.scrollTo(0, 0);
ga('send', 'pageview', to.fullPath);
});
export default router;
总结
- beforeEach:全局拦截导航,用于权限、标题、加载状态。
- beforeResolve:在异步组件加载完成后执行,适合预加载数据。
- afterEach:导航完成后清理或统计,无需控制流程。 通过合理使用这三个守卫,可以实现复杂的导航逻辑,提升应用的健壮性和用户体验。
router.back/forward
<template>
<button @click="router.back()">Back</button>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<template>
<button @click="router.forward()">Forward</button>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
</script>
你也可以使用router.go()
方法,它接受一个参数作为历史堆栈中要返回或前进的步骤数。例如,router.go(-2)将导航到后退两步的页面,而router.go(2)将向前跳转两步(如果它们存在)