loading...

Vue 3, Vuex 4 Modules, Typescript

shubhadip profile image shubhadip ・4 min read

In this article we will going to see how can we use typescript along with Vue 3 and Vuex 4 and Vuex Modules.

Note: Vuex 4 is still in beta version. Also along with removal of global this from vue, this.$store is also removed from vuex 4 and its upto developer to write typings for store.You can read more about from vuex-release-note

We will be creating modules like root and counter wherein counter will be module and root as RootState.Lets create a module folder inside store folder and an index.ts which will export our store.
Store Structure

we will also have interfaces.ts file which will have all interfaces for our store.

import { ActionContext } from "vuex";
import { MutationTypes as CounterMTypes } from "./modules/counter/mutation-types";
import { ActionTypes as CounterATypes } from "./modules/counter/action-types";

export interface IRootState {
  root: boolean;
  version: string;
}

export interface CounterStateTypes {
  counter?: number;
  rootDispatch?: boolean
}

export interface CounterGettersTypes {
  doubledCounter(state: CounterStateTypes): number;
  counterValue(state: CounterStateTypes): number;
}

export type CounterMutationsTypes<S = CounterStateTypes> = {
  [CounterMTypes.SET_COUNTER](state: S, payload: number): void;
  [CounterMTypes.RESET_COUNTER](state: S): void;
};

export type AugmentedActionContext = {
  commit<K extends keyof CounterMutationsTypes>(
    key: K,
    payload: Parameters<CounterMutationsTypes[K]>[1]
  ): ReturnType<CounterMutationsTypes[K]>;
} & Omit<ActionContext<CounterStateTypes, IRootState>, "commit">;

export interface CounterActionsTypes {
  [CounterATypes.GET_COUNTER](
    { commit }: AugmentedActionContext,
    payload: number
  ): void;
}
Enter fullscreen mode Exit fullscreen mode

so CounterActionsTypes acts as interface for actions in counter module, CounterMutationsTypes acts as interface for mutations in counter module and CounterGettersTypes acts as interface for getters.You can read more about utility like Omit etc from typescript-utility

Lets start by creating counter module.

// store/modules/counter/state.ts
import { CounterStateTypes } from "./../../interfaces";

export const state: CounterStateTypes = {
  counter: 0,
};

// store/modules/counter/action-types.ts
export enum ActionTypes {
  GET_COUNTER = "GET_COUNTER"
}

// store/modules/counter/mutation-types.ts
export enum MutationTypes {
  SET_COUNTER = "SET_COUNTER",
  RESET_COUNTER = "RESET_COUNTER"
}

// store/modules/counter/getters.ts

import { GetterTree } from "vuex";
import {
  CounterGettersTypes,
  CounterStateTypes,
  IRootState
} from "./../../interfaces";

export const getters: GetterTree<CounterStateTypes, IRootState> &
  CounterGettersTypes = {
  counterValue: (state: CounterStateTypes) => {
    return state.counter || 0;
  },
  doubledCounter: (state: CounterStateTypes) => {
    return state.counter || 0 * 2;
  }
};

// store/modules/counter/mutations.ts
import { MutationTree } from "vuex";
import { MutationTypes } from "./mutation-types";
import { CounterMutationsTypes, CounterStateTypes } from "./../../interfaces";

export const mutations: MutationTree<CounterStateTypes> &
  CounterMutationsTypes = {
  [MutationTypes.SET_COUNTER](state: CounterStateTypes, payload: number) {
    state.counter = payload;
  },
  [MutationTypes.RESET_COUNTER](state: CounterStateTypes) {
    state.counter = 0;
  }
};

// store/modules/counter/actions.ts
import { ActionTree } from "vuex";
import { ActionTypes } from "./action-types";
import { MutationTypes } from "./mutation-types";
import {
  CounterActionsTypes,
  CounterStateTypes,
  IRootState
} from "@/store/interfaces";

export const actions: ActionTree<CounterStateTypes, IRootState> &
  CounterActionsTypes = {
  [ActionTypes.GET_COUNTER]({ commit }, payload: number) {
    commit(MutationTypes.SET_COUNTER, payload);
  }
};

// store/modules/counter/index.ts
import { Module } from "vuex";
import { CounterStateTypes, IRootState } from "@/store/interfaces";
import { getters } from "./getters";
import { actions } from "./actions";
import { mutations } from "./mutations";
import { state } from "./state";

// Module
const counter: Module<CounterStateTypes, IRootState> = {
  state,
  getters,
  mutations,
  actions
};

export default counter;
Enter fullscreen mode Exit fullscreen mode

Now that we have created module counter we have to add types, lets create types.ts in counter module

// store/modules/counter/types.ts
import {
  CounterStateTypes,
  CounterMutationsTypes,
  CounterGettersTypes,
  CounterActionsTypes
} from "@/store/interfaces";
import { Store as VuexStore, CommitOptions, DispatchOptions } from "vuex";

export type CounterStoreModuleTypes<S = CounterStateTypes> = Omit<
  VuexStore<S>,
  "commit" | "getters" | "dispatch"
> & {
  commit<
    K extends keyof CounterMutationsTypes,
    P extends Parameters<CounterMutationsTypes[K]>[1]
  >(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<CounterMutationsTypes[K]>;
} & {
  getters: {
    [K in keyof CounterGettersTypes]: ReturnType<CounterGettersTypes[K]>;
  };
} & {
  dispatch<K extends keyof CounterActionsTypes>(
    key: K,
    payload?: Parameters<CounterActionsTypes[K]>[1],
    options?: DispatchOptions
  ): ReturnType<CounterActionsTypes[K]>;
};
Enter fullscreen mode Exit fullscreen mode

So this creates our counter module along with its types.Lets now focus on root, we will be creating a root folder inside modules which will be used as root for vuex store.
store structure

This is how our folder structure will look like after this.
only extra thing that we have to do in root module is to add modules to it rest all is similar to counter module

// store/modules/root/index.ts

import { Module, ModuleTree } from "vuex";
import { IRootState } from "@/store/interfaces";
import { getters } from "./getters";
import { actions } from "./actions";
import { mutations } from "./mutations";
import { state } from "./state";
import counterModule from "../counter";

// Modules
const modules: ModuleTree<IRootState> = {
  counterModule,
};

const root: Module<IRootState, IRootState> = {
  state,
  getters,
  mutations,
  actions,
  modules
};

export default root;
Enter fullscreen mode Exit fullscreen mode

you can see we have added modules to root and we can pass this to createStore.

so with this done now we can set up index.ts in our store folder

import { createStore } from "vuex";
import { IRootState } from "@/store/interfaces";
import { CounterStoreModuleTypes } from "./modules/counter/types";
import { RootStoreModuleTypes } from "./modules/root/types";

import root from "./modules/root";

export const store = createStore<IRootState>(root);

type StoreModules = {
  counter: CounterStoreModuleTypes;
  root: RootStoreModuleTypes;
};

export type Store = CounterStoreModuleTypes<Pick<StoreModules, "counter">> &
  Counter1StoreModuleTypes<Pick<StoreModules, "counter1">> &
  RootStoreModuleTypes<Pick<StoreModules, "root">>;

Enter fullscreen mode Exit fullscreen mode

createStore<IRootState>(root); with this we are marking root module as rootState.Store will act as type for our whole store.

I have also created an action-types.ts and mutation-types.ts inside store folder so that we can have all actions and mutation at one place.

// store/action-types.ts
import { ActionTypes as counterTypes } from "./modules/counter/action-types";
import { ActionTypes as rootATypes } from "./modules/root/action-types";

export const AllActionTypes = { ...counterTypes, ...rootATypes };

// store/mutation-types.ts
import { MutationTypes as counterTypes } from "./modules/counter/mutation-types";
import { MutationTypes as rootMTypes } from "./modules/root/mutation-types";

export const AllMutationTypes = {...counterTypes,...rootMTypes };

Enter fullscreen mode Exit fullscreen mode

Note: since i am spreading types, in case of same action/mutation types might override. we can prevent this by using namespace or completely avoiding this.

This completes our store,lets see how we can use our store in components.
we will be creating a useStore utility in src/use folder.

import { Store } from "@/store";

import { useStore as VuexStore } from "vuex";
/**
 * Returns Whole Store Object
 */
export function useStore(): Store {
  return VuexStore() as Store;
}
Enter fullscreen mode Exit fullscreen mode

Now we can import useStore directly in our views and components.

type support

type support

In this way we can have type support for store as well as we added modules in store along with types.

Note : I have skipped few steps of creating store, all code regarding this article is present at Repository.
Articles which i went through before being able to get modules working vuex-typescript

Discussion

pic
Editor guide