Vue 3, Vuex 4 Modules, Typescript
shubhadip
Posted on October 29, 2020
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.
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;
}
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;
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]>;
};
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.
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;
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">>;
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 };
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;
}
Now we can import useStore directly in our views and components.
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
Posted on October 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.