Using the Vue3’s composition API in Vue2

Reading Time: 4 minutes

Vue3 is officially out since September 18 and along with it comes the composition API which is replacing the old options API. The new composition is defined as a set of additive, function-based APIs that allow the flexible composition of component logic.

We are introduced to the composition API because with the old options API as the component grows the readability of the components was not quite pleasant or comfortable and was starting to get messy, the code reuse patterns had some drawbacks, the support for TypeScript was limited etc.
The visual comparison of both APIs look something like this:

First, we must install @vue/composition-api via Vue.use() before using other APIs.
Import the VueCompositionApi:

import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Register the plugin:

Vue.use(VueCompositionApi)

And then we are ready to start.

So, the new API must contain the setup() function which serves as the entry point for using the composition API inside the components. setup() is called before the beforeCreate hook. The component would look like this:

<template>
    <div> {{ note }} </div>
    <div> {{ data.count }} </div>
</template>

<script>
import { ref, computed, reactive, onMounted } from '@vue/composition-api'

export default {
  setup() {
    const notes = ref([])
    async function getData() {
      notes.value = await DataService.getNotesData()
    }

    const data = reactive({
      count: 0,
      actions: ['firstAction', 'secondAction', 'thirdAction'],
      object: {
        foo: 'bar'
      }
    })
    const computedData = computed(() => notes.value)
    
    onMounted(() => {
      getData()
    }

    return {
      ...toRefs(data),
      notes,
      computedData
    }
  }
}
</script>

In the code above we can see the structure of the new API. We have the setup() function, which is exported in the script tag.
Inside the setup function we can see several familiar properties.

const notes = ref([])

This is initializing a property inside the setup function scope. On this property, we must add ref if we want to make it reactive, which means if we don’t add the ref and we make a change to that variable, the change won’t be reflected in the DOM. This is the same as initializing variable in the data() in Vue2:

data() {
  return {
    notes: []
  }
}

As we can see we do not have the method section for creating functions, instead we define the functions in the setup(). After that, the method is used in the mounted hook which is a bit different than the one in the options API.

Some of the hooks were removed, but almost all of them are available in a similar form.

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • activated -> onActivated
  • deactivated -> onDeactivated
  • errorCaptured -> onErrorCaptured

In addition, two new debug hooks were added to the composition API:

  • onRenderTracked
  • onRenderTriggered

Computed and watch are still available. In the code above we can see how computed is used to return the notes values.

Reactive() is similar to ref() and if we want to create a reactive object we can still use ref(). But underneath the hood, it’s just calling reactive(). On the other side, reactive() will not work with primitive values. It takes an object and returns a reactive proxy of the original. The big difference is when you want to access data defined using reactive(), for example, if we want to use the count, we must do it like:

<div> {{ data.count }} </div>

One very important thing is to convert a reactive object to plain object is using the toRefs and return the data like in the component:

return {
  ...toRefs(data)
}

This is just a small piece of the cake, only something to begin with using the composition API in Vue2 application. For more, you can visit the documentation on the following link.

Typescript/ES7 Decorators to make Vuex modules a breeze

Reading Time: 5 minutes

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:

Weekly downloads on npmjs.com

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.