Make your VueJS code look more fabulous with class decorator (part 2)

Khai Bui,VueJSTip

In part 1 of this article, I introduced you to how to make your Vue code look more fabulous than before what you did by using decorators. But in reality, most projects usually use the store management library like Vuex for complexity state through all projects. So, if we just use the decorators for the main code will not be enough. Let's complete this mission.

How to use it?

Firstly, you must already have a project using Vue 3. Then download the package from NPM:

npm install vuex-module-decorators

ES5 transpilation

This package is distributed with ES2015 style code. This means it is perfectly fine for modern browsers (Chrome, Firefox, Safari), but will not work on IE11. If you are using IE11 you are probably having a Babel-based transpilation setup to generate ES5 code. If that's the case, you need to make sure this package also gets transpiled.

Add this in your vue.config.js (Vue CLI v3):

module.exports = {
  transpileDependencies: ['vuex-module-decorators'],
}

Transpiler configurations

  • Babel 6/7 - You need to install babel-plugin-transform-decorators
  • Set experimentalDecorators: true in your ts.config.json
  • For reduced code with decorators, set importHelpers: true
  • Set emitHelpers: true (only for Typescript 2)

The features and performances

  • Typescript classes with strict type safety: create modules where nothing can go wrong. Compile time type-checking ensures you cannot mutate data not part of the module, or access unavailable fields.
  • Decorators for declarative code: annotate your functions with @Action or @Mutation to automatically turn then 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.

Get started

Define a module

To define a module, create a class that extends from VuexModule and must be decorated with Module decorator:

/app/store/mymodule.ts
import { Module, VuexModule } from 'vuex-module-decorators'
 
@Module
export default class MyModule extends VuexModule {
  someField: string = 'somedata'
}
⚠️

There is a Module class in the vuex package too, which is not a decorator. Make sure you import the correct Module decorator from vuex-module-decorators

Use in store

In your store, you use the MyModule class itself as a module.

import Vuex from 'vuex'
import MyModule from '~/store/mymodule'
 
const store = new Vuex.Store({
  modules: {
    myMod: MyModule,
  },
})

The way we use the MyModule class is different from classical object-oriented programming and similar to how vue-class-component works. We use the class itself as a module, not an object constructed by the class.

Access state

All the usual ways of accessing the module work:

  1. Import from store
import store from '~/store'
...
store.state.myMod.someField
  1. Use this.$store if in component
this.$store.state.myMod.someField
  1. Use getModule() to create a type-safe accessor
import { Module, VuexModule, getModule } from 'vuex-module-decorators'
import store from '@/store'
 
@Module({ dynamic: true, store, name: 'mymod' })
class MyModule extends VuexModule {
  someField: number = 10
}
const myMod = getModule(MyModule)
myMod.someField //works
myMod.someOtherField //Typescript will error, as a field doesn't exist

Core concept

State

All properties of the class are converted into state props.

import { Module, VuexModule } from 'vuex-module-decorators'
 
@Module
export default class Vehicle extends VuexModule {
  wheels = 2
}

Getters

All ES6 getter functions of the class are converted into vuex getters.

import { Module, VuexModule } from 'vuex-module-decorators'
 
@Module
export default class Vehicle extends VuexModule {
  wheels = 2
 
  get axles() {
    return this.wheels / 2
  }
}

Mutations

All functions decorated with @Mutation are converted into Vuex mutations.

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

Actions

All functions that are decorated with @Action are converted into Vuex actions.

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

MutationActions

If you have understood how Actions and Mutations work you might have requirements for some functions that:

  • Firstly, do an asynchronous action.
  • Then commit the resultant value to the store via a mutation.

This is where a @MutationAction comes into the picture.

import {VuexModule, Module, MutationAction} from 'vuex-module-decorators'
 
@Module
class TypicodeModule extends VuexModule {
  posts: Post[] = []
  users: User[] = []
 
  @MutationAction
  async function updatePosts() {
    const posts = await axios.get('https://jsonplaceholder.typicode.com/posts')
    return { posts }
  }
}

Note that if S denotes the type of state, then the object returned from a MutationAction function must be of type Partial<S> The keys present inside the return value (for eg, here posts) are replaced into the store. When a MutationAction function returns undefined, the mutation part of the MutationAction will not be called, and the state will remain the same.

Conclusion

Now, we finished the complete style to create Vue 3.0 project using decorator in Vue code and state management. I hope it could help you find out a new way to implement your next project in the future. You can reference official documentation for further information and advanced topics at:

Reference resources

Vue-module-decorator documentation

© Bùi Quốc Khải.