Vuex TypeScript 支持实战:为 $store 属性与 useStore 函数提供完整类型
2026/9/19 20:17:12 网站建设 项目流程

Vuex TypeScript 支持实战:为 $store 属性与 useStore 函数提供完整类型

【免费下载链接】vuex🗃️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex

本文围绕 Vuex 官方文档中的 TypeScript 支持指南展开,系统讲解如何在 TypeScript 项目中为 Vuex 4 的三个关键点提供类型:直接编写带类型的 Store 定义、为选项式组件中的this.$store属性声明模块扩展、以及为组合式 API 的useStore函数返回带类型的 Store。读完本文,你将掌握从InjectionKey定义到app.use注入的完整类型链路,并结合 Vuex 源码(src/injectKey.js、src/store.js)理解这些类型声明背后的运行时机制。

Vuex 自带类型定义,无需额外 TypeScript 配置

Vuex 以库的形式内置了完整的类型声明(typings),因此你可以直接用 TypeScript 编写 Store 定义,不需要任何针对 Vuex 的特殊 TypeScript 配置。项目本身的类型入口指向 types/index.d.ts,package.json 中通过"typings": "types/index.d.ts"声明,并将types/index.d.tstypes/helpers.d.tstypes/logger.d.tstypes/vue.d.ts一并发布(files字段)。对使用者而言,只需按照 Vue 3 官方文档中「基本的 TypeScript 配置」来配置自己的项目即可。

在类型层面,Vuex 暴露的核心导出包括:

  • Store<S>类与createStore<S>(options: StoreOptions<S>): Store<S>工厂函数,其中泛型S即 Store 的 state 类型;
  • useStore<S = any>(injectKey?: InjectionKey<Store<S>> | string): Store<S>组合式函数;
  • StoreOptions<S>GetterTreeActionTreeMutationTreeModule等用于描述完整 Store 结构的接口,见 types/index.d.ts。

也就是说,只要你在createStore/new Store时给出 state 的接口定义,store.state就会获得精确类型(readonly state: S),dispatchcommitsubscriberegisterModule等 API 也都有对应的签名。

为 Vue 组件中的$store属性提供类型

虽然 Vuex 在运行时会自动把 Store 挂载到组件实例上——从 src/store.js 的install方法可以看到:

install (app, injectKey) { app.provide(injectKey || storeKey, this) app.config.globalProperties.$store = this // ... }

但 Vuex 并没有开箱即用地为this.$store提供类型。用 TypeScript 编写 Vue 组件时,你必须自己声明模块扩展(module augmentation)。

具体做法:在项目的文件夹中新增一个声明文件(例如vuex.d.ts),对 Vue 的ComponentCustomProperties接口进行自定义类型声明:

// vuex.d.ts import { Store } from 'vuex' declare module '@vue/runtime-core' { // 声明你的 store state interface State { count: number } // 为 `this.$store` 提供类型 interface ComponentCustomProperties { $store: Store<State> } }

这里有两个值得注意的实现细节:

  1. 为什么扩展目标是@vue/runtime-core而不是vue。Vue 3 的运行时核心类型(包括ComponentCustomPropertiesComponentCustomOptions等可混入接口)都定义在@vue/runtime-core包中,vue包只是再导出它们。Vuex 自身的类型文件 types/vue.d.ts 正是对同一个模块做扩展,但它只声明了ComponentCustomOptions中的store?: Store<any>(用于createApp(Component, { store })这种传参写法),并没有覆盖$store,所以后者仍需用户自己声明。
  2. State接口的复用。上面声明在@vue/runtime-core模块内的interface State只是该示例文档的写法;实际项目中通常把State接口单独定义并从vuex.d.ts中导入,避免声明合并带来的意外。

声明生效后,在选项式组件中this.$store.state.count即可被识别为numberthis.$store.commit/this.$store.dispatch也会走 types/index.d.ts 中Dispatch/Commit接口的重载签名(同时支持'type', payload{ type, payload }两种调用风格)。

useStore组合式函数提供类型

用组合式 API 编写 Vue 组件时,你通常希望useStore返回带类型的 Store。为了让useStore正确返回带类型的 Store,需要完成三步:

  1. 定义带类型的InjectionKey
  2. 向 Vue App 安装 Store 时传入这个带类型的InjectionKey
  3. 把这个带类型的InjectionKey传给useStore方法。

下面逐步说明。

第一步:定义带类型的 InjectionKey 并创建 Store

// store.ts import { InjectionKey } from 'vue' import { createStore, Store } from 'vuex' // 定义 store state 的类型 export interface State { count: number } // 定义注入 key export const key: InjectionKey<Store<State>> = Symbol() export const store = createStore<State>({ state: { count: 0 } })

第二步:安装 Store 时传入 InjectionKey

// main.ts import { createApp } from 'vue' import { store, key } from './store' const app = createApp({ ... }) // 传入注入 key app.use(store, key) app.mount('#app')

第三步:在组件中通过 key 获取带类型的 Store

// 在 Vue 组件中 import { useStore } from 'vuex' import { key } from './store' export default { setup () { const store = useStore(key) store.state.count // 类型是 number } }

源码视角:为什么 InjectionKey 是核心

在内部实现上,Vuex 使用 Vue 的 Provide/Inject 机制把 Store 安装到 Vue App 实例上,这正是InjectionKey能承担类型传递职责的原因。从源码可以完整还原这条链路:

  • src/injectKey.js 定义了默认 key 与useStore
export const storeKey = 'store' export function useStore (key = null) { return inject(key !== null ? key : storeKey) }

即:传入 key 时用你传入的 key 去inject,否则回退到字符串'store'

  • src/store.js 的install方法与之一一对应:app.provide(injectKey || storeKey, this)—— 你通过app.use(store, key)传入的第二个参数就是injectKey,Provider 端和 Inject 端必须使用同一个 key,类型才能沿 Provide/Inject 通道精确传递。

  • 类型声明与之一致:types/index.d.ts 中Store.install的签名为install(app: App, injectKey?: InjectionKey<Store<any>> | string): void,而useStore的签名是:

export function useStore<S = any>(injectKey?: InjectionKey<Store<S>> | string): Store<S>;

注意injectKey允许传入string,这是因为运行时storeKey本身是字符串'store'。仓库自带的类型测试 types/test/index.ts 中的UseStoreFunction命名空间验证了全部四种调用形态:传入InjectionKey<string>形式的 key、传入字符串'store'、直接传泛型useStore<State>()(此时返回Store<State>)、以及不传任何参数的useStore()(泛型默认any,返回未类型化的 Store)——这也解释了为什么文档强调:想要类型,就必须把带类型的InjectionKey从定义、安装到使用一路传下去。

简化useStore的用法

每次使用useStore时都要导入InjectionKey并把它传给useStore,很快就会变成重复劳动。更简洁的做法是定义一个自己的组合函数来封装这个 key:

// store.ts import { InjectionKey } from 'vue' import { createStore, useStore as baseUseStore, Store } from 'vuex' export interface State { count: number } export const key: InjectionKey<Store<State>> = Symbol() export const store = createStore<State>({ state: { count: 0 } }) // 定义你自己的 `useStore` 组合函数 export function useStore () { return baseUseStore(key) }

这样,组件侧只需导入自己的组合函数,就不需要再提供注入 key 及其类型即可拿到带类型的 Store:

// 在 Vue 组件中 import { useStore } from './store' export default { setup () { const store = useStore() store.state.count // 类型是 number } }

由于baseUseStore(key)key的类型是InjectionKey<Store<State>>baseUseStore的泛型S会被推断为State,返回类型自然就是Store<State>。这个模式相当于把「三步走」中第 1、2 步固化到 Store 模块内,第 3 步的调用成本降为零。

类型正确性的验证方式

Vuex 仓库自身通过tsc对上述类型定义做回归验证:package.json 的test:types脚本执行tsc -p types/test,编译 types/test/index.ts 与 types/test/tsconfig.json(开启了"strict": true"noEmit": true)。该测试文件覆盖了三条与本文直接相关的链路:

  • new Vuex.Store<...>各 API(dispatch/commit/watch/subscribe/subscribeAction/replaceState/registerModule/hotUpdate)的调用形态,见 types/test/index.ts 与 types/test/index.ts;
  • useStore的 key 字符串、InjectionKey、泛型直传三种形态,见 types/test/index.ts;
  • ActionContext<S, R>在模块(含 namespaced 模块)中state/rootState/getters/rootGetters的推断,见 types/test/index.ts。

你可以在自己的项目中参照这套思路:先用tsc --noEmit检查 Store 定义,再在组件侧验证this.$storeuseStore返回值的类型是否符合预期。

小结

场景需要的类型工作关键机制
用 TS 编写 Store 定义无额外配置,直接用createStore<State>/new Store<State>泛型S贯穿StoreOptions<S>
选项式组件访问this.$storevuex.d.ts中扩展@vue/runtime-coreComponentCustomProperties运行时由app.config.globalProperties.$store挂载(见 src/store.js)
组合式 API 使用useStore定义InjectionKey<Store<State>>app.use(store, key)useStore(key)Provide/Inject 通道(见 src/injectKey.js)
简化组合式 API 用法在 Store 模块内封装一个闭包持有 key 的useStore()泛型推断自InjectionKey类型

需要留意的前提:本仓库对应 Vuex 4.x(当前版本 4.1.0),peerDependencies要求vue ^3.2.0,因此上述app.use(store, key)InjectionKey等写法均基于 Vue 3 应用;若你的项目仍在使用 Vue 2 + Vuex 3,类型方案与注入机制均不相同,本文内容不适用。

【免费下载链接】vuex🗃️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询