Overview
Who does not like to use Vuex in a VueJS App? I think no one ๐
Today I would like to show you a very useful tool written in TypeScript that can boost your productivity with Vuex: vuex-module-decorators. Lately, itโs getting more popular. Below you can see the weekly downloads of the package which raise up constantly:
But what does it exactly do and which benefit does it provide?
- Typescript classes with strict type of safety
Create modules where nothing can go wrong. The type check at compile time ensures that you cannot mutate data that is not part of the module or cannot access unavailable fields. - Decorators for declarative code
Annotate your functions with@Action
or@Mutation
to automatically turn them into Vuex module methods. - Autocomplete for actions and mutations
The shape of modules is fully typed, so you can access action and mutation functions with type-safety and get autocomplete help.
In short, with this library, you can write Vuex module in this format:
import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'
import { get } from 'axios'
@Module
export default class Posts extends VuexModule {
posts: PostEntity[] = [] // initialise empty for now
get totalComments (): number {
return posts.filter((post) => {
// Take those posts that have comments
return post.comments && post.comments.length
}).reduce((sum, post) => {
// Sum all the lengths of comments arrays
return sum + post.comments.length
}, 0)
}
@Mutation
updatePosts(posts: PostEntity[]) {
this.posts = posts
}
@Action({commit: 'updatePosts'})
async function fetchPosts() {
return get('https://jsonplaceholder.typicode.com/posts')
}
}
As you can see, thanks to this package, we are able to write a Vuex module by writing a class which provides Mutations, Actions and Getters. Everything in one single file. How cool is that?
Benefits of type-safety
Instead of using the usual way to dispatch and commit…
store.commit('updatePosts', posts)
await store.dispatch('fetchPosts')
…with the getModule
Accessor you can now use a more type-safe mechanism that does not offer type safety for the user data and no help for automatic completion in IDEs.
import { getModule } from 'vuex-module-decorators'
import Posts from `~/store/posts.js`
const postsModule = getModule(Posts)
// access posts
const posts = postsModule.posts
// use getters
const commentCount = postsModule.totalComments
// commit mutation
postsModule.updatePosts(newPostsArray)
// dispatch action
await postsModule.fetchPosts()
Core concepts
State
All properties of the class are converted into state props. For example, the following code
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
}
is equivalent to this:
export default {
state: {
wheels: 2
}
}
Mutations
All functions decorated with @Mutation
are converted into Vuex mutations. For example, the following code
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
puncture(n: number) {
this.wheels = this.wheels - n
}
}
is equivalent to this:
export default {
state: {
wheels: 2
},
mutations: {
puncture: (state, payload) => {
state.wheels = state.wheels - payload
}
}
}
Actions
All functions that are decorated with @Action
are converted into Vuex actions.
For example this code
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { get } from 'request'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
addWheel(n: number) {
this.wheels = this.wheels + n
}
@Action
async fetchNewWheels(wheelStore: string) {
const wheels = await get(wheelStore)
this.context.commit('addWheel', wheels)
}
}
is equivalent to this:
const request = require(โrequest')
export default {
state: {
wheels: 2
},
mutations: {
addWheel: (state, payload) => {
state.wheels = state.wheels + payload
}
},
actions: {
fetchNewWheels: async (context, payload) => {
const wheels = await request.get(payload)
context.commit('addWheel', wheels)
}
}
}
Advanced concepts
Namespaced Modules
If you intend to use your module in a namespaced way, then you need to specify so in the @Module
decorator:
@Module({ namespaced: true, name: 'mm' })
class MyModule extends VuexModule {
wheels = 2
@Mutation
incrWheels(extra: number) {
this.wheels += extra
}
get axles() {
return this.wheels / 2
}
}
const store = new Vuex.Store({
modules: {
mm: MyModule
}
})
Registering global actions inside namespaced modules
In order to register actions of namespaced modules globally, you can add a parameter root: true
to @Action
and @MutationAction
decorated methods:
@Module({ namespaced: true, name: 'mm' })
class MyModule extends VuexModule {
wheels = 2
@Mutation
setWheels(wheels: number) {
this.wheels = wheels
}
@Action({ root: true, commit: 'setWheels' })
clear() {
return 0
}
get axles() {
return this.wheels / 2
}
}
const store = new Vuex.Store({
modules: {
mm: MyModule
}
})
This way the @Action
clear of MyModule
will be called by dispatching clear although being in the namespaced module mm
. The same thing works for @MutationAction
by just passing { root: true }
to the decorator-options.
Dynamic Modules
Modules can be registered dynamically simply by passing a few properties into the @Module
decorator, but an important part of the process is, we first create the store, and then pass the store to the module:
import store from '@/store'
import {Module, VuexModule} from 'vuex-module-decorators'
@Module({dynamic: true, store, name: 'mm'})
export default class MyModule extends VuexModule {
/*
Your module definition as usual
*/
}
Installation
The installation of the package is quite simple and does not require many steps:
Download the package
npm install vuex-module-decorators
# or
yarn add vuex-module-decorators
Vue configuration
// vue.config.js
module.exports = {
// ... your other options
transpileDependencies: [
'vuex-module-decorators'
]
}
For more details, you can check the plugin’s official documentation.
Conclusion
I personally think this package can rump up your productivity because it embraces the “modularisation” pattern by making your app more scalable. Another big advantage is the fact you have “type-checking” thankfully to TypeScript. If you have a VueJS TypeScript Application, I strongly recommend you this package.
Thanks for this amazing article! it has been written very well!
Thanks for sharing this and giving me some ideas to rewrite our web application with Typescript.
I’ve experienced some troubles that I’ve finally passed through.
First place, JS console was telling me to use getModule passing as a second parameter the full store reference itself:
const userModule: UserModule = getModule(UserModule, store)
If I place this property inside my vue class component it won’t show any associated data. I have not found why this happens but placing it outside the class seems to solve this behaviour.
In my case it was mandatory to put a name in @Module decorator. Without it will fail when served.
Thanks again for this small guide to type Vuex, hope we can move soon to Vue 3 where it seems it is way easier to integrate Typescript.