Pinia 和 Vue Router 都是 Vue 官方生态的核心库,并且都提供了完善的 TypeScript 支持。在项目中使用时,正确的类型标注能让编辑器智能提示变得非常精准,大幅减少“拼错字段”、“传错参数”之类的低级错误。
Pinia 的类型集成
Pinia 在设计之初就充分考虑了 TypeScript,它不需要额外的类型包装文件,类型推断几乎可以覆盖所有常见场景。
1. 定义 Store 时自动推断类型
使用组合式 API 语法定义 Store 时,返回值的类型会被自动推断,开发者通常不需要手动写类型注解:
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const name = ref<string>('Alice')
const age = ref<number>(30)
const isAdult = computed(() => age.value >= 18)
function updateName(newName: string) {
name.value = newName
}
return { name, age, isAdult, updateName }
})
在组件中使用时,useUserStore() 返回的对象拥有完整的类型提示:访问 name.value 时编辑器知道它是一个 string,调用 updateName 时就会校验参数类型。如果误写成 updateName(123),TypeScript 会直接报错。
2. 选项式 Store 的类型声明
对于选项式 API 风格的 Store,Pinia 也支持通过 TypeScript 接口来定义 State 的类型:
import { defineStore } from 'pinia'
interface UserState {
name: string
age: number
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
name: 'Alice',
age: 30
}),
getters: {
isAdult: (state) => state.age >= 18
},
actions: {
updateName(newName: string) {
this.name = newName
}
}
})
3. 使用 Getter 的类型推断
Pinia 会正确推断 Getter 的返回类型,因此在组件中通过 store.isAdult 就能得到 boolean 类型。对于接受参数的 Getter,也只需在函数参数上标注类型即可:
getters: {
getUserById: (state) => (id: number) => {
// ...
}
}
4. 使用插件时的类型扩展
Pinia 插件可以为所有 Store 注入通用属性(如 $router),这时需要通过 TypeScript 的 声明合并 来扩展 PiniaCustomProperties 接口,才能获得类型提示:
// 在项目的全局类型声明文件中
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
$router: import('vue-router').Router
}
}
这样所有 Store 实例上都可以使用 this.$router 且获得完整类型。
Vue Router 的类型集成
Vue Router 4 对 TypeScript 的支持也很深入,主要体现在路由配置的类型安全、路由参数的类型推断以及导航守卫的类型标注。
1. 路由配置的类型声明
定义路由时,为每个路由的 meta 字段提供统一的类型接口,可以让后续访问时获得正确提示:
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
// 扩展 RouteMeta 接口
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
title?: string
}
}
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('../views/Home.vue'),
meta: { title: '首页' }
},
{
path: '/admin',
component: () => import('../views/Admin.vue'),
meta: { requiresAuth: true, title: '后台' }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
这样在任何地方通过 route.meta 访问时,TypeScript 都能提示出 requiresAuth 和 title 字段,并且会校验类型。
2. 路由参数与 Query 的类型推断
在组件中通过 useRoute 获取路由信息时,可以使用泛型参数来精确推断 params 和 query 的结构:
import { useRoute } from 'vue-router'
// 假设路由路径为 /user/:id?tab=xxx
const route = useRoute()
// 直接获取 params.id 的类型默认是 string | string[]
// 如果需要更精细的类型,可以在路由配置中为 params 定名
对于动态路由参数,Vue Router 会记录路由定义时的 name 和 params,在编程式导航时提供类型约束。例如使用 router.push:
router.push({ name: 'user', params: { id: '123' } })
如果 params 字段写错或缺失,TypeScript 会报错。这种类型安全需要借助 unplugin-vue-router 或手动通过 RouteNamedMap 接口来声明所有具名路由的参数表,不过即使不这样做,基本的 Router 和 RouteLocationNormalized 类型已经能覆盖多数情况。
3. 导航守卫的类型
全局守卫(如 beforeEach)的回调参数已经自带类型,开发者可以直接使用 to、from 的属性:
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'login' }
}
})
在组件内守卫(如 beforeRouteEnter)中,参数同样是强类型的,无需额外标注。
4. 在组件中使用 useRouter 和 useRoute
组合式 API 中,两个函数的返回类型会自动推导:
import { useRouter, useRoute } from 'vue-router'
const router = useRouter() // Router 类型
const route = useRoute() // RouteLocationNormalizedLoaded 类型
无需手动标注泛型,直接用就好。
两者的协同
当 Pinia Store 中需要访问路由信息时,可以在 action 中通过 this.$router(需要声明合并)或者直接引入 useRouter()(组合式 Store 更推荐直接在 setup 中调用),两边类型系统可以无缝对接。
简单说:Pinia 和 Vue Router 的 TypeScript 集成都是“默认就好,需要深度定制时才做一次声明扩展”。日常开发中你几乎不会为此多写类型,但安全感却能大幅提升——参数传错、字段拼错、守卫逻辑错误,在编译阶段就能被发现,而不是部署后才爆出问题。