Is it possible to include types? Absolutely. However, the decision to add types now or wait for vuex
to provide better types is a separate consideration.
Straightforward Approach
Here's a method to introduce type hints for 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 approach has some drawbacks: it lacks elegance and doesn't offer full type checking if a mutation
or action
requires a payload
Elaborate Solution
This solution incorporates type hints for the key
parameter of both commit
and dispatch
, along with 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>
}
While not very user-friendly, 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 provides typehint of string for payload
store.commit('changeFoo', 'bar')
The same principles apply for dispatch, but it outputs a Promise<any>
Final Thoughts
You can incorporate types, although the optimal scenario would involve vuex
revamping their types for improved type hinting (assuming it aligns with their project objectives).
Additional Information
This code was written using vs-code
along with the following packages:
typescript@^4.5.5
vuex@^4.0.2
vue@^3.2.31