Vue3 中让 Vuex 的 useStore 具有完整的 state 和 modules 类型推测

22,479 阅读2分钟

如今 vue3 已经接近尾声,各方面也得到了完善,对ts的支持也更强了。可是 vuex4 对 ts 的支持却没有任何改变,使用 useStore 的类型仍然为 any,在此官方提供了解决方案。

InjectionKey 注入类型

  1. 定义类型InjectionKey。
  2. InjectionKey在将商店安装到Vue应用程序时提供类型。
  3. 将类型传递InjectionKey给useStore方法。
// store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'

// 手动声明 state 类型
export interface State {
  count: number
}

// 定义注入类型
export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  state: {
    count: 0
  }
})

接下来,安装到Vue应用程序时传递定义的注入类型:

// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({ ... })

// pass the injection key
app.use(store, key)

app.mount('#app')

最后,您可以将密匙传递给useStore方法以检索类型化的存储。

// in a vue component
import { useStore } from 'vuex'
import { key } from './store'

export default {
  setup () {
    const store = useStore(key)

    store.state.count // typed as number
  }
}

使用 declare module 覆盖 useStore 类型

可以看见,通过定义一个特殊的密匙,useStore就可以根据这个密匙查找对应储存的类型,并返回正确的类型,但这有个比较麻烦的一点,就是必须得手动定义 State 的类型,也就是说当写state的时候,还要在写多一份 state 接口,而且官方实例忽略了 modules 中 state 的类型,这里在下有个更好的方式解决这两个问题

  1. 根据当前 store 改造 useStore 类型
  2. 使用提供的类型数据改造 modules 文件的类型
  3. 使用 declare module ""vuex/types" 覆盖传入泛型
// store.ts
import { createStore } from 'vuex'
const store = createStore({
  state: {
    count: 1
  },
  mutations: {},
  getters: {},
  actions: {},
});
// 这里可以得到当前 store.state 的类型
type StoreStateType = typeof store.state
// modules/common.ts
// 改造一个泛型函数, 只用于在当前储存中保持类型正确
import { StoreOptions, Store } from "vuex";
const createModule = <S>(store: StoreOptions<S>) => store as Store<S>;
// 定义一个 module
export default createModule({
  state: {
    commonCount: 12
  }
});

在让我们回到 store.ts 中,利用 declare module 覆盖 useStore 的类型

// store.ts
import { createStore } from "vuex";
import common from "./modules/common";

const store = createStore({
  state: {
    count: 1
  },
  mutations: {},
  getters: {},
  actions: {},
  modules: {
    common
  }
});
// 覆盖原有 useStore 函数中, 泛型默认值类型
declare module "vuex" {
  type StoreStateType = typeof store.state;
  type ModulesType = {
    common: typeof common.state;
  }
  export function useStore<S = StoreStateType & ModulesType>(): Store<S>;
}

export default store;

到了这一步就已经完成的对当前 state和 module 的 state 类型推测了,至于 dispatch,commit 中的魔法字符串还有 any getters,我还没有找到有效的解决方法,官方类型中定的比较固定,很难暴露出actions|getters|mutations中的 key 值类型,就很难顾及到当中的类型,又让dispatch,commit不在是任意的字符串,请教一下各位有好的解决方案吗?