一步步完善vuex4的commit, dispatch 的 ts语法提示

2,802 阅读3分钟

vuex4commit,dispatch似乎不支持直接语法提示

故在项目的store基于vuexmodules的情况下,对项目进行了改造

创建项目

使用vue-cli创建项目,勾选上tsvuex

修改下,使用modules

我们修改下文件,初始的vuex的结构大致是这样的

// store/index.ts

import { createStore } from 'vuex'
import modules from './modules'
export default createStore({
  modules: modules,
})

// main.ts 中

import store from './store'
createApp(App).use(store)

// store/modules/index.ts

import user from "./user";
const modules = {
  user,
}
export default modules

// store/modules/user.ts

import { ActionContext } from "vuex";
export interface State { // 定义该模块的state类型
  name: string;
  token: string;
}
export const _state = {
  name: "",
  token: "",
};
export const mutations = {
  setName(state: State, params: string) {
    state.name = params;
  },
  setToken(state: State, params: string) {
    state.token = params;
  },
};
export const actions = {
  login({ commit }: ActionContext<State, any>) { // 目前没导出RootState,暂时先用any代替
    commit("setToken", "token");
  },
};
export default {
  namespaced: true,
  state: _state,
  mutations,
  actions,
};

定义类型

准备工作已经做的差不多,接下来我们先在store/modules/index.ts文件下定义RootState,用于提供给其他地方使用

// 在 store/modules/index.ts 中添加如下代码
import user, { State as UserState} from "./user";
import other, { State as OtherState} from "./other"; // 如果有其他modules,则这么写

export interface RootState {
  user: UserState,
  other: OtherState // 如果有其他modules,则这么写
}

store.state类型推断

我们取到modules返回到RootState,来作为state的类型,定义key值来使项目使用这个store

// store/index.ts

import { createStore, Store, useStore as baseUseStore } from "vuex";
import modules, { RootState } from './modules'
import { InjectionKey } from 'vue'

export default createStore<RootState>({ // 此处是为了让store在代码中直接引用取值时加的推断
  modules: modules,
});

// 定义key,用于在main.ts 中使用
export const key: InjectionKey<Store<RootState>> = Symbol()

export function useStore() {
  return baseUseStore(key)
}


// main.ts
import store, {key} from "./store";
createApp(App).use(store, key).use(router).mount("#app");

为了在路由文件等地方中获取vuex数据,使用createStore<RootState>来添加推断 import store from "@/store"; console.log(store.state.user.name)

此时我们就能愉快的在vue文件中引用导出的useStore,使用state的语法推断了。

为什么要使用导出的useStore而不直接引用vuex的,这是为了少点代码,否则你可能需要这么写

import { useStore } from 'vuex'
import { key } from '@/store'

setup() {
    const store = useStore(key)
    console.log(store.state.user.name)
}

image.png

推断modules里面的 mutationsactions 的值

store下新建types.ts

此处代码借鉴了ssh大佬的文章 陪尤雨溪一起,实现 Vuex 无限层级类型推断。 改造(瞎改)了下

import { Store as BaseStore, CommitOptions, DispatchOptions } from 'vuex'
import modules, { RootState } from './modules'

type RootType = typeof modules  // 推断modules类型

type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`

type Split<S extends string, D extends string> = string extends S
  ? any[]
  : S extends ''
  ? []
  : S extends `${infer T}${D}${infer U}`
  ? [T, ...Split<U, D>]
  : [S]

type GetMutations<Module> = Module extends { mutations: infer M } ? M : never
type GetActions<Module> = Module extends { actions: infer M } ? M : never

type GetModuleMutationKeys<Module, Key> = AddPrefix<Key, keyof GetMutations<Module>>
type GetModuleActionsKeys<Module, Key> = AddPrefix<Key, keyof GetActions<Module>>

type GetModulesMutationKeys<Modules> = {
  [K in keyof Modules]: GetModuleMutationKeys<Modules[K], K>
}[keyof Modules]

type GetModulesActionsKeys<Modules> = {
  [K in keyof Modules]: GetModuleActionsKeys<Modules[K], K>
}[keyof Modules]

type Commit = GetModulesMutationKeys<RootType>
type CommitFn = {
  // 不知道怎么将 Split<F, '/'> 里的值与RootType进行优雅的绑定,先用笨方法直接写死
  [F in Commit]: RootType[Split<F, '/'>[0]]['mutations'][Split<F, '/'>[1]]
}
type Dispatch = GetModulesActionsKeys<RootType>
type DispatchFn = {
  [F in Dispatch]: RootType[Split<F, '/'>[0]]['actions'][Split<F, '/'>[1]]
}

export type Store = Omit<
  BaseStore<RootState>,
  // 'getters' |
  'commit' | 'dispatch'
> & {
  commit<K extends keyof CommitFn, P extends Parameters<CommitFn[K]>[1]>(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<CommitFn[K]>
} & {
  dispatch<K extends keyof DispatchFn>(
    key: K,
    payload?: Parameters<DispatchFn[K]>[1],
    options?: DispatchOptions
  ): ReturnType<DispatchFn[K]>
  // } & {
  //   getters: {
  //     [K in keyof Getters]: ReturnType<Getters[K]>
  //   }
}

改造store/index.ts 让vuex的类型强制为自己修改的类型

import { createStore, Store, useStore as baseUseStore } from "vuex";
import modules, { RootState } from './modules'
import { InjectionKey } from 'vue'
import { Store as Store1 } from './types'

export default createStore<RootState>({
  modules: modules,
});  as Store1

// 定义key,用于在main.ts 中使用
export const key: InjectionKey<Store<RootState>> = Symbol()

export function useStore() {
  return baseUseStore(key)  as Store1
}

image.png image.png

虽然代码丑了点,但最终还是实现了目标,短时间可以凑合凑合用着

代码还有很大瑕疵,待我ts大法学成后~😄

源码