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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。