📋 核心类型分类与关系
重要说明
在深入了解各个类型之前,需要明确一些重要的类型关系:
RouteLocationNormalized和RouteLocationNormalizedLoaded都继承自同一个基础接口。RouteLocationNormalizedLoaded是RouteLocationNormalized的加载完成版本,确保异步组件等已解析完毕。RouteRecordNormalized是独立的路由记录类型,代表路由配置的标准化形式,与路由位置类型不同。
1. 路由配置阶段
RouteRecordRaw
这是你在创建路由时使用的原始配置类型,用于定义路由的基本结构。这些原始记录在传递给 Vue Router 后会被标准化处理。
const routes: RouteRecordRaw[] = [
{
path: '/user/:id',
name: 'User',
component: UserComponent,
meta: { requiresAuth: true },
children: [
{
path: 'profile',
component: UserProfile
}
]
}
];
RouteRecordNormalized
RouteRecordRaw 经过 Vue Router 内部标准化处理后的路由记录。它包含了完整的、规范化的路由配置信息。注意:这与 RouteLocationNormalized 是不同的类型! RouteLocationNormalized 的 matched 属性会包含一个 RouteRecordNormalized 数组。
// 通过 to.matched 访问匹配的路由记录
router.beforeEach((to) => {
// to.matched 是一个 RouteRecordNormalized 数组
const matchedRecord: RouteRecordNormalized = to.matched[0];
if (matchedRecord) {
console.log(matchedRecord.path); // 路由路径模板,如 "/user/:id"
console.log(matchedRecord.name); // 路由名称
console.log(matchedRecord.components); // 组件配置 (可能是多个命名视图)
console.log(matchedRecord.children); // 子路由 (RouteRecordNormalized[])
console.log(matchedRecord.meta); // 路由元信息
}
});
2. 路由导航阶段
RouteLocationRaw
用于编程式导航(如 router.push 或 router.replace)的参数类型,支持多种形式的路由描述。
// 字符串形式
const destination1: RouteLocationRaw = '/user/123';
// 对象形式
const destination2: RouteLocationRaw = {
name: 'User',
params: { id: '123' },
query: { tab: 'profile' },
hash: '#details'
};
// 使用
router.push(destination1);
router.replace(destination2);
RouteLocationNormalized
完整解析后的路由位置信息,包含所有标准化的路由数据。这是路由导航守卫(如 beforeEach, beforeResolve, afterEach)中 to 和 from 参数的核心类型。
// 在路由守卫中最常见
router.beforeEach((
to: RouteLocationNormalized,
from: RouteLocationNormalized
) => {
console.log({
path: to.path, // "/user/123" (解析后的实际路径)
name: to.name, // "User"
params: to.params, // { id: "123" }
query: to.query, // { tab: "profile" }
meta: to.meta, // { requiresAuth: true }
matched: to.matched, // RouteRecordNormalized[] 匹配的路由记录数组
fullPath: to.fullPath, // "/user/123?tab=profile"
hash: to.hash, // "#details"
redirectedFrom: to.redirectedFrom // 重定向来源 (RouteLocationNormalized)
});
});
RouteLocationNormalizedLoaded
组件内通过 useRoute() 获取的当前路由信息,是响应式的路由对象。它继承自 RouteLocationNormalized,但关键区别在于它代表一个导航已完成且所有相关组件(包括异步组件)都已加载的路由状态。
// 在组件中使用
import { useRoute } from 'vue-router';
import { defineComponent, watchEffect } from 'vue';
export default defineComponent({
setup() {
const route: RouteLocationNormalizedLoaded = useRoute();
// 响应式访问路由信息,此时所有组件都已加载
watchEffect(() => {
console.log('当前用户ID:', route.params.id);
console.log('当前完整路径:', route.fullPath);
});
return { route };
}
});
3. 实用类型
RouteParams系列
-
RouteParams
-
经过解析的路由参数对象
-
使用场景:获取当前路由的动态参数
// 路由: /user/:id const params: RouteParams = route.params // { id: '123' }
-
-
RouteParamsGeneric
-
泛型路由参数,提供类型安全
-
在TypeScript中定义具体的参数类型
interface UserParams extends RouteParamsGeneric { id: string tab?: string } // 类型安全的参数访问 const userParams = route.params as UserParams
-
-
RouteParamsRaw
-
原始路由参数对象
-
编程式导航时传入参数
router.push({ name: 'user', params: { id: 123 } as RouteParamsRaw })
-
-
RouteParamsRawGeneric
-
泛型原始路由参数
-
提供类型安全的原始参数设置
interface UserParamsRaw extends RouteParamsRawGeneric { id: string | number } const params: UserParamsRaw = { id: 123 }
-
-
RouteParamValue
-
单个路由参数的值
-
当你确定参数只有一个值时使用
const userId: RouteParamValue = route.params.id // string
-
-
应用场景
// 列表页面的分页和筛选 const handleSearch = (filters: any) => { const query: LocationQueryRaw = { page: 1, pageSize: 20, keyword: filters.keyword, category: filters.categories, sortBy: 'created_at' } router.push({ query }) } // 获取查询参数 const currentQuery: LocationQuery = route.query const page = Number(currentQuery.page) || 1// 用户详情页路由定义 // /user/:id/profile/:tab? // 访问参数 const params: RouteParams = route.params const userId = params.id // string const activeTab = params.tab // string | undefined // 编程式导航 router.push({ name: 'UserProfile', params: { id: '123', tab: 'settings' } as RouteParamsRaw })
RouteQuery系列
-
LocationQuery
-
经过解析的查询参数对象,值已被规范化
-
当你需要访问已解析的查询参数时,比如在组件中获取当前路由的查询参数
// 获取当前路由的查询参数 const query: LocationQuery = route.query // { page: '1', sort: 'name', tags: ['vue', 'router'] }
-
-
LocationQueryRaw
-
原始的查询参数对象,值未经处理
-
当你需要设置路由参数时,可以传入原始值,Vue Router会自动处理
// 导航时传入原始查询参数 router.push({ path: '/users', query: { page: 1, active: true } as LocationQueryRaw })
-
-
LocationQueryValue
-
单个查询参数的值(已解析)
-
当你确定某个查询参数只有一个值时使用
const pageParam: LocationQueryValue = route.query.page // string | null
-
-
单个查询参数的原始值
-
单个查询参数的原始值
-
设置查询参数时,可以传入多种类型的值
const queryParams = { page: 1, // number search: 'vue', // string active: null // null } as Record<string, LocationQueryValueRaw>
-
-
RouteQueryAndHash
-
包含查询参数和hash的对象
-
当你需要同时设置查询参数和hash时
router.push({ path: '/docs', ...{ query: { section: 'api' }, hash: '#methods' } as RouteQueryAndHash })
-
查询参数的类型定义。查询参数的值可以是字符串或字符串数组 (当同一参数名出现多次时)。
// 查询参数总是字符串或字符串数组
const query: RouteQuery = {
page: '1',
size: '10',
tags: ['vue', 'router'] // 例如 ?tags=vue&tags=router
};
🎯 选择指南
1. 使用 RouteRecordRaw 的场景
✅ 适用于:
- 定义初始路由配置数组时 (
createRouter中的routes选项) - 使用
router.addRoute()动态添加单个路由或子路由时 - 构建路由表的原始结构数据时
// 定义路由配置
const userRoutes: RouteRecordRaw[] = [
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: { title: '个人资料' }
},
{
path: '/settings',
name: 'Settings',
component: Settings,
meta: { title: '设置' }
}
];
// 动态添加路由 (作为顶级路由)
router.addRoute(userRoutes[0]);
// 动态添加子路由
router.addRoute('User', { path: 'preferences', name: 'UserPreferences', component: UserPreferences });
// 批量添加 (通常在初始化时作为 routes 数组传递)
// userRoutes.forEach(route => {
// router.addRoute(route); // 如果要添加为顶级路由
// });
2. 使用 RouteLocationNormalized 的场景
✅ 适用于:
- 路由守卫 (
beforeEach,beforeResolve,afterEach) 中处理to和from路由信息 - 需要访问完整、解析后的路由信息(包括
matched记录、fullPath等)进行逻辑判断或数据提取 - 开发路由中间件或插件,分析和操作路由导航
// 全局前置守卫
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized,next:NavigationGuardNext) => {
// 权限检查
if (to.meta?.requiresAuth && !isAuthenticated()) {
return { name: 'Login', query: { redirect: to.fullPath }, replace: true };
}
// 页面标题设置
if (to.meta?.title && typeof to.meta.title === 'string') {
document.title = to.meta.title;
}
next();
});
// 路由解析守卫
router.beforeResolve(async (to: RouteLocationNormalized) => {
// 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用
// 适合执行一些最终的检查或数据预取
console.log('即将导航到 (已解析异步组件):', to.fullPath);
if (to.meta.needsData) {
// await fetchDataForRoute(to);
}
});
3. 使用 RouteLocationNormalizedLoaded 的场景
✅ 适用于:
- 在 Vue 组件内部通过
useRoute()(Composition API) 或this.$route(Options API) 获取当前激活的路由信息 - 当需要响应式地访问当前路由的
params,query,meta,path等,并确保这些信息是导航完成后的最终状态 - 组件内依赖路由数据进行渲染、计算属性或侦听变化
// 组合式 API
import { defineComponent, computed, watch } from 'vue';
import { useRoute, useRouter, type RouteLocationNormalizedLoaded } from 'vue-router';
export default defineComponent({
setup() {
const route: RouteLocationNormalizedLoaded = useRoute(); // 关键!
const router = useRouter();
// 响应式计算属性
const userId = computed(() => route.params.id as string);
const currentTab = computed(() => (route.query.tab as string) || 'info');
// 监听路由参数变化
watch(() => route.params.id, (newId, oldId) => {
if (newId && newId !== oldId) {
// loadUserData(newId);
}
});
// 监听整个路由对象变化 (更通用,但需注意性能)
// watch(route, (newRoute, oldRoute) => { ... });
return { userId, currentTab };
}
});
// 选项式 API
export default {
computed: {
userId(): string {
// this.$route 的类型是 RouteLocationNormalizedLoaded
return this.$route.params.id as string;
},
currentTab(): string {
return (this.$route.query.tab as string) || 'info';
}
},
watch: {
'$route.params.id': {
handler(newId: string, oldId: string) {
if (newId && newId !== oldId) {
// this.loadUserData(newId);
}
},
immediate: true // 可选,组件创建时立即执行
}
}
};
4. 使用 RouteLocationRaw 的场景
✅ 适用于:
- 编程式导航 (
router.push(),router.replace()) 的参数 - 在代码中动态构建导航链接或目标
- 定义
<router-link>的toprop (虽然通常直接写对象或字符串,但其内部也接受RouteLocationRaw)
// 编程式导航函数
const navigateToUser = (userId: string, tab?: string) => {
const destination: RouteLocationRaw = {
name: 'User', // 推荐使用 name 进行导航
params: { id: userId },
query: tab ? { tab } : undefined // query 可以是 undefined
};
router.push(destination);
};
// 条件导航
const handleNavigation = (pathOrName: string, replace = false) => {
const destination: RouteLocationRaw = pathOrName.startsWith('/') ? pathOrName : { name: pathOrName };
if (replace) {
router.replace(destination);
} else {
router.push(destination);
}
};
// 复杂路由构建
const buildProductRoute = (categoryId: string, productId: string): RouteLocationRaw => {
return {
name: 'ProductDetail',
params: { categoryId, productId },
query: { from: 'search' },
hash: '#reviews'
};
};
// router.push(buildProductRoute('electronics', 'tv-123'));
关键区别说明
1. RouteRecordNormalized vs RouteLocationNormalized
RouteRecordNormalized: 代表一个路由配置记录的标准化形式。它是关于路由“应该如何”匹配和渲染的定义。它包含path(通常带参数占位符,如/user/:id)、components、meta、children等配置信息。RouteLocationNormalized: 代表一个实际发生的、具体化的路由位置。它是关于“当前在哪里”或“要去哪里”的信息。它包含解析后的path(如/user/123)、具体的params(如{ id: '123' })、query、hash,以及一个matched数组(其中包含与当前位置匹配的RouteRecordNormalized对象)。
// RouteRecordNormalized - 路由配置记录 (定义)
const userRouteRecord: RouteRecordNormalized = {
path: '/user/:id', // 路径模板
name: 'User',
components: { default: UserComponent }, // 或单个组件
children: [], // 子路由记录 (RouteRecordNormalized[])
meta: { requiresAuth: true },
// ... 其他配置属性如 props, beforeEnter 等
leaveGuards: new Set(),
updateGuards: new Set(),
enterCallbacks: {},
instances: {}, // 运行时组件实例
aliasOf: undefined,
pathToRegexpOptions: undefined, // 路径匹配选项
redirect: undefined
};
// RouteLocationNormalized - 当前或目标路由位置信息 (实例)
const userRouteLocation: RouteLocationNormalized = {
path: '/user/123', // 解析后的实际路径
fullPath: '/user/123?tab=profile#info',
name: 'User',
params: { id: '123' }, // 解析后的参数
query: { tab: 'profile' },
hash: '#info',
meta: { requiresAuth: true }, // 从 matched 记录中合并而来
matched: [/* ..., userRouteRecord */], // 包含匹配的 RouteRecordNormalized
redirectedFrom: undefined,
};
2. RouteLocationNormalized vs RouteLocationNormalizedLoaded
RouteLocationNormalized: 这是在路由守卫 (beforeEach,beforeResolve,afterEach) 中to和from参数的类型。在这个阶段,异步组件可能尚未加载完成,导航也可能还在进行中或即将被取消/重定向。RouteLocationNormalizedLoaded: 这是在组件内部通过useRoute()或this.$route获取的当前路由对象类型。它保证了导航已经完成,所有相关的异步组件和路由层面的beforeResolve守卫已经执行完毕。它是RouteLocationNormalized的一个“稳定”版本,可以安全地用于组件渲染和响应式数据。
// 在路由守卫中 - 可能还在加载中,导航可能未最终确认
router.beforeEach((to: RouteLocationNormalized) => {
// 此时 to.matched 中的异步组件可能还未解析
console.log('导航目标:', to.path);
});
// 在组件 setup 中 - 确保已加载完成
// const route: RouteLocationNormalizedLoaded = useRoute();
// 此时 route 对象代表的是一个稳定和完全解析的路由状态。
3. 实际使用中的差异 (常见误区)
// ❌ 不准确的理解或类型标注
router.beforeEach((to) => { // to 默认类型是 RouteLocationNormalized
// to.matched[0] 不是 RouteLocationNormalized 类型!
// const record: RouteLocationNormalized = to.matched[0]; // 类型错误,且逻辑上不通
});
// ✅ 正确的理解和类型使用
router.beforeEach((to: RouteLocationNormalized) => {
// 'to' 是 RouteLocationNormalized,代表目标路由位置
const location: RouteLocationNormalized = to;
// 'to.matched' 是一个 RouteRecordNormalized 数组
// 'to.matched[0]' (如果存在) 是一个 RouteRecordNormalized 对象,代表匹配到的路由配置记录
if (to.matched.length > 0) {
const record: RouteRecordNormalized = to.matched[0];
console.log('匹配到的路由记录路径模板:', record.path); // e.g., /user/:id
console.log('当前实际路径:', location.path); // e.g., /user/123
}
});
📝 快速记忆口诀
记住这个简单的规则,帮助区分核心类型:
- 配置用
Raw(原始) -RouteRecordRaw(定义路由配置时) - 导航用
Raw(原始) -RouteLocationRaw(调用router.push/replace时) - 守卫用
Normalized(标准化) -RouteLocationNormalized(导航守卫中的to/from) - 组件用
Loaded(已加载) -RouteLocationNormalizedLoaded(组件内useRoute()或this.$route) - 记录是
Record(标准化配置) -RouteRecordNormalized(标准化后的单个路由配置对象, 存在于matched数组中)