Can you add types? Yes you can. Whether or not you should add types (instead of waiting for vuex to add better types) is a different question.
Basic Solution
Here is a way to add type hints to the commit and dispatch methods of the store instance:
import { createStore, useStore as baseUseStore, Store, ActionHandler, DispatchOptions, CommitOptions } from 'vuex';
import { InjectionKey } from 'vue';
export interface State {
foo: string;
}
export const key: InjectionKey<Store<State>> = Symbol();
const storeInitializer = {
state: { foo: 'foo' } as State,
mutations: {
changeFoo(state: State, payload: string) {
state.foo = payload
},
rearrangeFoo(state: State) {
state.foo = state.foo.split('').sort().join()
}
},
actions: {
setFooToBar: (({ commit }) => {
commit('changeFoo', 'bar')
}) as ActionHandler<State, State>
}
}
export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
dispatch: (type: keyof T['actions'], payload?: any, options?: DispatchOptions) => Promise<any>
commit: (type: keyof T['mutations'], payload?: any, options?: CommitOptions) => void
}
export const store = createStore<State>(storeInitializer)
export function useStoreTyped() {
const keyedStore = baseUseStore(key);
return keyedStore as typeof keyedStore & TypedDispatchAndAction<typeof storeInitializer>
}
This method has some downsides: It's not very graceful, and it doesn't do full type checking if a mutation or action requires a payload
Complex Solution
This solution adds type hints for the key parameter of both commit and dispatch as well as adding type checking for the payload parameter
import { createStore, useStore as baseUseStore, Store, DispatchOptions, CommitOptions, ActionContext } from 'vuex';
import { InjectionKey } from 'vue';
type Length<L extends any[]> = L['length']
type Function<P extends any[] = any, R extends any = any> = (...args: P) => R
type ParamLength<Fn extends Function> = Length<Parameters<Fn>>
type P1<Fn extends (...args: any) => any> = Parameters<Fn>[1]
type Payload<H extends Function> = ParamLength<H> extends 2
? P1<H> extends infer X ? X : never
: never
type TypedDispatchOrCommit<A extends { [key: string]: Function }, Options, Return, K extends keyof A = keyof A> = {
<I extends K>(key: I, ...args: {
0: [],
1: [payload: Payload<A[I]>]
}[Payload<A[I]> extends never ? 0 : 1]): Return
<I extends K>(key: I, ...args: {
0: [payload: undefined, options: Options],
1: [payload: Payload<A[I]>, options: Options]
}[Payload<A[I]> extends never ? 0 : 1]): Return
}
export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
dispatch: TypedDispatchOrCommit<T['actions'], DispatchOptions, Promise<any>>
commit: TypedDispatchOrCommit<T['mutations'], CommitOptions, void>
}
export interface State {
foo: string;
}
export const key: InjectionKey<Store<State>> = Symbol();
const storeInitializer = {
state: { foo: 'foo' } as State,
mutations: {
changeFoo(state: State, payload: string) {
state.foo = payload
},
rearrangeFoo(state: State) {
state.foo = state.foo.split('').sort().join()
}
},
actions: {
setFooToBar({ commit }: ActionContext<State, State>) {
commit('changeFoo', 'bar')
},
setFoo({ commit }: ActionContext<State, State>, payload: string) {
commit('changeFoo', payload)
}
}
}
export const store = createStore<State>(storeInitializer)
export function useStoreTyped() {
const keyedStore = baseUseStore(key);
return keyedStore as Omit<typeof keyedStore, 'dispatch' | 'commit'> & TypedDispatchAndAction<typeof storeInitializer>
}
Despite its downside of not being very intelligible, the above code does pass the following tests:
import { useTypedStore } from '../store'
const store = useTypedStore()
// expected: Pass, actual: Pass, returns void
store.commit('rearrangeFoo')
// expected: Fail, actual: Fail, returns void
store.commit('changeFoo')
// expected: Pass, actual: Pass, returns void
// also gives typehint of string for payload
store.commit('changeFoo', 'bar')
The same is true for dispatch, except it returns a Promise<any>
Conclusion
You can get types, but the best solution is for vuex to overhaul their types for better type hinting (assuming that fits their project vision).
Notes
This was all done on vs-code with the following packages:
typescript@^4.5.5
vuex@^4.0.2
vue@^3.2.31