人人都会AI编程

18.3 Pinia、Vue Router 的类型集成

更新时间:2026-07-11

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 都能提示出 requiresAuthtitle 字段,并且会校验类型。

2. 路由参数与 Query 的类型推断

在组件中通过 useRoute 获取路由信息时,可以使用泛型参数来精确推断 paramsquery 的结构:

import { useRoute } from 'vue-router'

// 假设路由路径为 /user/:id?tab=xxx
const route = useRoute()

// 直接获取 params.id 的类型默认是 string | string[]
// 如果需要更精细的类型,可以在路由配置中为 params 定名

对于动态路由参数,Vue Router 会记录路由定义时的 nameparams,在编程式导航时提供类型约束。例如使用 router.push

router.push({ name: 'user', params: { id: '123' } })

如果 params 字段写错或缺失,TypeScript 会报错。这种类型安全需要借助 unplugin-vue-router 或手动通过 RouteNamedMap 接口来声明所有具名路由的参数表,不过即使不这样做,基本的 RouterRouteLocationNormalized 类型已经能覆盖多数情况。

3. 导航守卫的类型

全局守卫(如 beforeEach)的回调参数已经自带类型,开发者可以直接使用 tofrom 的属性:

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 集成都是“默认就好,需要深度定制时才做一次声明扩展”。日常开发中你几乎不会为此多写类型,但安全感却能大幅提升——参数传错、字段拼错、守卫逻辑错误,在编译阶段就能被发现,而不是部署后才爆出问题。