I am working on a Vue 3 project with TypeScript and Vuex4. Right now I am using a boilerplate declaration method for every store module in in vuex with TS. Which looks like this: Or if my code is not readable enough, here is what i used to guide me: https://betterprogramming.pub/the-state-of-typed-vuex-the-cleanest-approach-2358ee05d230
//#region Store
export const state: State = {
stateVar: null,
isError: false,
};
export const getters: GetterTree<State, RootState> & Getters = {
getStateVar: state => state.stateVar,
getIsError: state => state.isError,
};
export const mutations: MutationTree<State> & Mutations = {
[ModuleMutationTypes.setStateVar](state: State, payload: StateVarType) {
state.stateVar = payload;
},
[ModuleMutationTypes.setIsError](state: State, payload: boolean) {
state.isError = payload;
},
};
export const actions: ActionTree<State, RootState> & Actions = {
async [ModuleActionTypes.doRequest]({ commit, getters }) {
//do something and commit it
// for example here i would like to access the getters or action from another store how is that possible
commit(ModuleMutationTypes.setStateVar, someValue);
},
};
//#endregion Store
//#region Store Type
export type State = {
someStateVar: SomeStateVarType;
isError: boolean;
};
export type Getters = {
getStateVar(state: State): someStateVarType;
getIsError(state: State): boolean;
};
export type Mutations<S = State> = {
[ModuleMutationTypes.setStateVar](state: S, payload: SomeSTateVarType): void;
[ModuleMutationTypes.setIsError](state: S, payload: boolean): void;
};
export interface Actions {
[ModuleActionTypes.doRequest](context: AugmentedActionContext): Promise<any>;
}
type AugmentedActionContext = {
commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>;
getters: {
[K in keyof Getters]: ReturnType<Getters[K]>;
};
dispatch<K extends keyof Actions>(
key: K,
payload?: Parameters<Actions[K]>[1],
options?: DispatchOptions
): ReturnType<Actions[K]>;
} & Omit<ActionContext<State, RootState>, 'commit' | 'getters' | 'dispatch'>;
//#endregion Store Type
//#region Store Module
export type ModuleStore<S = State> = Omit<VuexStore<S>, 'getters' | 'commit' | 'dispatch'> & {
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
key: K,
payload: P,
options?: CommitOptions
): ReturnType<Mutations[K]>;
} & {
dispatch<K extends keyof Actions>(
key: K,
payload?: Parameters<Actions[K]>[1],
options?: DispatchOptions
): ReturnType<Actions[K]>;
} & {
getters: {
[K in keyof Getters]: ReturnType<Getters[K]>;
};
};
export const store: Module<State, RootState> = {
state,
mutations,
getters,
actions,
//namespaced also breaks everything
// But without it, a bigger store might have clashes in namings
// namespaced: true,
};
//#endregion Store Type & Module
Now as you see this makes me repeat lots and lots of stuff, but until Vuex 5 i dont know if there is any other better solution. Also I am very new to TypeScript, I just used this boilerplate as a template to make my store typed.
My main questions are:
Even if you cant provide a complete answer i am happy to try every suggestion you guys have since i am quite lost.
As for name-spacing, well that's still an open-ended topic, feel free to share.
As the answer did not include the getters, after some trial and error I ended up doing this:
//./store/index.ts
import {
Getters as AxiosGetters,
} from '@/store/modules/axios/axios';
import {
Getters as SiteSettingsGetters,
} from '@/store/modules/site_settings/site_settings';
import {
Getters as UserGetters
} from '@/store/modules/user/user';
export interface RootGetters extends AxiosGetters, SiteSettingsGetters, UserGetters {}
//./some-store/module.ts
import {RootGetters} from '@/store';
type AugmentedActionContext = {
commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>;
getters: {
[K in keyof Getters]: ReturnType<Getters[K]>;
};
rootGetters: {
[K in keyof RootGetters]: ReturnType<RootGetters[K]>;
};
dispatch<K extends keyof Actions>(
key: K,
payload?: Parameters<Actions[K]>[1],
options?: DispatchOptions
): ReturnType<Actions[K]>;
} & Omit<ActionContext<State, RootState>, 'commit' | 'getters' | 'dispatch' | 'rootGetters'>;
export interface Actions {
[ActionTypes.doSomething](context: AugmentedActionContext): Promise<void>;
}
export const actions: ActionTree<State, RootState> & Actions = {
[ActionTypes.doSomething]({ commit, rootGetters, dispatch }) {
// You can now use getters From other stores, and also the mutation and action useing All(name)Types and having them typed
},
To access other module's, you can define each module's action/mutation types and import all of them like this.
example ActionTypes
// store/action-types.ts
import { ActionTypes as rootActionTypes } from "./modules/root/action-types";
import { ActionTypes as authUserActionTypes } from "./modules/authUser/action-types";
import { ActionTypes as counterActionTypes } from "./modules/counter/action-types";
export const AllActionTypes = {
...authUserActionTypes,
...rootActionTypes,
...counterActionTypes,
};
and in components you can use like this.
import { useStore } from "../../composables/useStore";
import { AllActionTypes } from "../../store/action-types";
// setup()
const store = useStore();
onMounted(async () => {
await store.dispatch(AllActionTypes.GET_USERS).catch((e) => {
console.log(e);
});
await store.dispatch(AllActionTypes.GET_COUNT).catch((e) => {
console.log(e);
});
});
For your second question, this is how I've been namespacing.
// store/index.ts
import { createStore } from "vuex";
import { IRootState } from "./interfaces";
import { AuthUserStoreModuleTypes } from "./modules/authUser/types";
import { CounterStoreModuleTypes } from "./modules/counter/types";
import { RootStoreModuleTypes } from "./modules/root/types";
// all the modules are registered in the root
import root from "./modules/root";
export const store = createStore<IRootState>(root);
// namespacing
type StoreModules = {
counter: CounterStoreModuleTypes;
root: RootStoreModuleTypes;
authUser: AuthUserStoreModuleTypes;
};
export type Store = CounterStoreModuleTypes<Pick<StoreModules, "counter">> &
RootStoreModuleTypes<Pick<StoreModules, "root">> &
AuthUserStoreModuleTypes<Pick<StoreModules, "authUser">>;