Vue Router TypeScript 基础配置咋做?
不少用 Vue 做项目的同学,现在都慢慢把技术栈切换到 TypeScript 了,毕竟 TS 能在编码阶段就提前发现类型错误,让代码更健壮,但碰到 Vue Router 的时候,总会犯嘀咕:路由配置咋写才能让 TS 发挥作用?动态路由参数咋做类型约束?导航守卫里的参数类型咋处理?今天就把 Vue Router 结合 TypeScript 的常见问题掰碎了讲,从基础到实战一步一步理清楚~
先解决最基础的安装和配置问题,首先得确保依赖装对,Vue Router 4.x 对 TS 的支持已经很友好了,所以先装包:
npm install vue-router@4
接下来创建路由实例,这时候要注意路由数组的类型,Vue Router 提供了 RouteRecordRaw 类型,用来定义单个路由记录的结构,比如这样写路由配置:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// 定义路由数组,每个元素是 RouteRecordRaw 类型
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: HomeView // 同步组件,直接引入
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue') // 异步组件,懒加载
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
这里 RouteRecordRaw 帮我们约束了每个路由对象的 path、name、component 等字段的类型,要是写错字段(比如把 path 写成 paths),TS 会直接报错,提前拦截错误。
然后在 Vue 组件里用 useRouter 和 useRoute 的时候,TS 也能提供类型提示,比如在组件里导航:
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
// 跳转路由,router.push 的参数类型会被 TS 约束
const goToAbout = () => {
router.push({ name: 'about' }) // 这里如果 name 写错,TS 会提示
}
// 读取当前路由参数,route.params 等也有类型提示
console.log(route.path) // 自动推断是 string 类型
}
}
不过光这样还不够细,比如路由的 meta 信息、动态路由参数的类型,得进一步配置——这就需要往下深挖啦。
动态路由匹配,TS 咋约束参数类型?
动态路由比如 /user/:id 这种,参数 id 的类型咋保证?举个例子,路由配置里写:
const routes: RouteRecordRaw[] = [
{
path: '/user/:id',
name: 'user',
component: () => import('../views/UserView.vue')
}
]
这时候在组件里用 useRoute() 拿到的 route.params.id 类型是 string | undefined(因为路由参数可能不存在),但实际业务中 id 应该是数字或者有明确格式,这时候得做类型约束。
类型断言
在组件里直接断言参数类型:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
// 断言 id 是 number 类型
const userId = Number(route.params.id) as number
// 后续用 userId 就有 number 类型提示了
}
}
但这种方法有点“暴力”,如果参数格式不对(id 是字母),运行时会出错,所以更推荐在路由配置阶段就明确参数类型。
扩展路由参数类型
Vue Router 允许我们通过泛型来扩展 RouteParams,比如在项目的类型声明文件(如 env.d.ts 或 types/router.d.ts)里:
import 'vue-router'
declare module 'vue-router' {
interface RouteParams {
// 对应路由 name: 'user' 的 params 结构
user: {
id: string // 这里根据业务,也可以是 number,注意和 path 里的 :id 对应
}
}
}
这样配置后,当用 router.push({ name: 'user', params: { id: '123' } }) 时,TS 会检查 params.id 是否符合 string 类型;如果传错类型(比如传 id: 123 数字),就会报错。
在组件里用 useRoute() 时,route.params 也会有对应的类型提示:
const route = useRoute() // route.params.id 会被推断为 string | undefined const userId = route.params.id // 类型是 string | undefined,需处理 undefined 情况
要是业务里 id 必须存在,可以结合路由配置的 strict 模式,或者在导航守卫里做校验,但 TS 能提前帮我们把类型边界划清楚~
导航守卫里的 TS 类型咋搞?
导航守卫分全局守卫、路由独享守卫、组件内守卫,每个守卫的参数都有对应的类型,用 TS 可以避免参数用错。
全局守卫(如 router.beforeEach)
全局前置守卫的参数是 to、from、next,它们的类型分别是 RouteLocation、RouteLocation、NavigationGuardNext,Vue Router 已经内置了这些类型,所以直接写:
router.beforeEach((to, from, next) => {
// to 和 from 都有完整的路由信息类型
if (to.meta.requiresAuth && !isAuthenticated()) {
next({ name: 'login' })
} else {
next()
}
})
但这里 to.meta.requiresAuth 可能会报“属性不存在”的错,因为默认 RouteMeta 是个空接口,这时候需要扩展 RouteMeta 类型(前面提过的方法):
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean // 标记是否需要授权 string // 页面标题,必传
}
}
这样在路由配置里,每个路由的 meta 必须包含 title,可选 requiresAuth:
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: HomeView,
meta: {
title: '首页', // 必须传,否则 TS 报错
requiresAuth: false
}
}
]
现在在导航守卫里,to.meta.title 就有类型提示了,也不会报错~
路由独享守卫(beforeEnter)
路由独享守卫写在单个路由配置里,参数类型和全局守卫类似:
const routes: RouteRecordRaw[] = [
{
path: '/profile',
name: 'profile',
component: () => import('../views/ProfileView.vue'),
beforeEnter: (to, from, next) => {
// 这里 to 和 from 的类型同样受约束
if (from.name === 'home') {
next()
} else {
next({ name: 'home' })
}
}
}
]
组件内守卫(如 onBeforeRouteEnter)
在 Composition API 里,组件内守卫用 onBeforeRouteUpdate、onBeforeRouteLeave 这些函数,它们的参数类型也会被 TS 识别:
import { onBeforeRouteUpdate } from 'vue-router'
export default {
setup() {
onBeforeRouteUpdate((to, from) => {
// to 和 from 类型正确,能拿到 meta、params 等信息
console.log(to.meta.title, from.params.id)
})
}
}
通过给守卫参数加上类型约束,能避免很多“传错参数”“读错属性”的低级错误,这也是 TS 结合路由的核心优势之一~
路由懒加载时,TS 咋处理异步组件类型?
路由懒加载用 () => import('../views/AboutView.vue') 这种写法,Webpack 之类的打包工具能自动拆分代码,但 TS 对这种动态导入的组件类型会不会有丢失?
Vue Router 会自动推断异步组件的类型,只要组件本身是用 TS 写的,类型就会传递过来。AboutView.vue 是这样的:
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div>{{ msg }}</div>
</template>
当路由配置里用 component: () => import('../views/AboutView.vue') 时,TS 能识别这个异步组件的 props 类型,如果在路由跳转时传参:
router.push({
name: 'about',
params: {}, // 如果有 params
query: {
msg: 'hello' // 这里如果 AboutView 需要 msg 是 string,TS 会检查
}
})
不过有时候为了更明确,也可以手动给异步组件指定类型,比如用 defineAsyncComponent 结合泛型:
import { defineAsyncComponent } from 'vue'
const AboutView = defineAsyncComponent<{ msg: string }>(() => import('../views/AboutView.vue'))
// 然后在路由配置里用 component: AboutView
这样就算组件在异步加载,TS 也能提前知道它的 props 类型,避免传参错误~
懒加载时还可以加 Webpack 的魔法注释来控制 chunk 名字,
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
TS 对这种注释是友好的,不会报错,还能配合打包工具优化~
怎么给路由做更细的类型强化?
前面讲了扩展 RouteMeta 和 RouteParams,但实际项目中,我们还可以用泛型让 useRoute() 和 useRouter() 返回更精确的类型。
给 useRoute 加泛型
useRoute() 可以传入泛型,指定当前路由的 params、query、meta 结构,比如我们有个用户路由,params 是 { id: string },meta 有 title 和 requiresAuth:
import { useRoute, RouteLocationNormalizedLoaded } from 'vue-router'
// 定义当前路由的类型
type UserRoute = RouteLocationNormalizedLoaded & {
params: { id: string }
meta: { title: string; requiresAuth: boolean }
}
export default {
setup() {
const route = useRoute<UserRoute>()
// route.params.id 是 string 类型,route.meta.title 是 string 类型
console.log(route.params.id, route.meta.title)
}
}
这样就不用依赖全局的 RouteParams 和 RouteMeta 扩展,在组件内更精确地约束类型,适合局部特殊的路由场景。
统一管理路由类型
可以把所有路由的 name 和对应的参数类型集中到一个文件里,router-types.ts:
export type RouteNames = 'home' | 'user' | 'about'
export type RouteParamsMap = {
home: never // 没有参数
user: { id: string }
about: { tab?: string } // 可选参数
}
export type RouteMetaMap = {
home: { title: '首页'; requiresAuth: false }
user: { title: '用户页'; requiresAuth: true }
about: { title: '关于页'; requiresAuth: false }
}
然后在路由配置和组件里引用这些类型:
// 路由配置
import { RouteNames, RouteParamsMap, RouteMetaMap } from './router-types'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home' as RouteNames, // 断言 name 类型
component: HomeView,
meta: {
title: '首页',
requiresAuth: false
} as RouteMetaMap['home'] // 断言 meta 类型
}
// 其他路由类似
]
// 组件内用 router.push
import { useRouter } from 'vue-router'
import { RouteNames, RouteParamsMap } from './router-types'
const router = useRouter()
router.push({
name: 'user' as RouteNames,
params: { id: '123' } as RouteParamsMap['user']
})
这种集中管理的方式,在团队协作时能保证路由名称、参数、元信息的类型统一,减少沟通成本~
实战中 TS 类型错误咋解决?
用 TS 结合 Vue Router 时,最头疼的就是各种类型报错,这里列几个常见场景和解决方法。
Property 'xxx' does not exist on type 'RouteParams'
比如访问 route.params.id 时,TS 报错说 id 不存在,原因是 RouteParams 默认是个宽泛的类型,没包含我们的自定义参数。
解决方法:扩展 RouteParams 接口(前面讲过的),或者在组件内用类型断言:
const userId = (route.params as { id: string }).id
更推荐全局扩展 RouteParams,一劳永逸:
declare module 'vue-router' {
interface RouteParams {
user: { id: string }
// 其他路由参数
}
}
导航时 params 类型不匹配
比如路由 user 需要 id: string,但跳转时传了 id: 123(数字),TS 没报错,但运行时可能出错。
解决方法:给 router.push 的参数加类型约束,用泛型指定路由名称和参数:
import { Router, RouteLocationNamedRaw } from 'vue-router'
type PushParams<Name extends RouteNames> = RouteLocationNamedRaw & {
name: Name
params: RouteParamsMap[Name]
}
const push = <Name extends RouteNames>(router: Router, params: PushParams<Name>) => {
router.push(params)
}
// 使用时
push(router, {
name: 'user',
params: { id: '123' } // 这里如果传数字,TS 会报错
})
meta 字段类型丢失
访问 to.meta.title 时,TS 说 title 不存在,因为默认 RouteMeta 是空接口。
解决方法:扩展 RouteMeta 接口,在项目的类型声明文件里:
declare module 'vue-router' {
interface RouteMeta { string
requiresAuth?: boolean
}
}
这样所有路由的 meta 都得符合这个结构,TS 会自动检查~
异步组件导入类型错误
动态导入组件时,TS 报“模块没有默认导出”之类的错,通常是因为组件用了 export default 还是 export const 不一致。
解决方法:确保组件用 export default 导出,或者在导入时处理:
component: () => import('../views/AboutView.vue').then(m => m.AboutView)
如果组件是用 <script setup> 写的,默认会有 default 导出,所以直接用 () => import('../views/AboutView.vue') 即可~
TS 让 Vue Router 更“聪明”了!
把 Vue Router 和 TypeScript 结合起来,核心是利用 TS 的类型系统,给路由配置、参数传递、导航守卫这些环节加上“安全锁”,从基础配置时的 RouteRecordRaw,到动态路由的参数约束,再到导航守卫的类型提示,TS 能在编码阶段就帮我们发现大部分错误,减少运行时 Bug。
而且通过扩展 RouteMeta、RouteParams 这些接口,还能让团队内的路由规则更统一,新人接手项目时也能通过类型提示快速理解路由逻辑,现在前端项目越来越复杂,TS + Vue Router 这种组合,既能享受 Vue 的开发效率,又能借助 TS 保证代码质量,未来肯定是主流玩法~
要是你在实际开发中还有其他关于 Vue Router + TS 的疑问,比如和 Pinia 结合时的路由类型处理,或者 SSR 场景下的路由类型,评论区随时交流呀~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


