Search code examples
typescriptvue.jsvuexvuejs3vuex4

How to use other store module getters/actions/mutations with Vuex 4 and TypeScript


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:

  1. How can i access other module's getters or actions or mutations in a particular module, i would even be happy if i could get the RootGetters and RootActions working, but i tried it but i could not make it work, but as i mentioned im a new to this, so maybe i didnt know better. Or is it just that easy that i import this exported const store: Module<State, RootState> ... that would make my code vene clunkier as when i want to use getters from 4 diffrent modules i need to import all 4?
  2. How can i make these modules namespaced, i have some quite sizeable stores, talking about like 500 lines and lots of stuff going on, so it would be nice to not have names clash? Maybe that would make it easier to use them in other modules?

Even if you cant provide a complete answer i am happy to try every suggestion you guys have since i am quite lost.


**UPDATE:** You can see the accepted answer on how to use the allAction|MutationTypes, just import that instead of the submodule specific Action|MutationTypes and use that when calling dispatch or commit.

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
  },


Solution

  • 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">>;