Vuex With Class Components

Helloooo, in this post I'll show you how you can use vuex with TypeScript and class-components.

Disclaimer

In this tutorial I will be using:

  • Vue 2
  • Vuex ^3.6.2
  • TypeScript 4.5

What we're gonna build

Creating the project

Now let's start coding! First we have to create our Vue.js app. To do that run:

vue create vuex-counter

and make sure you include Vuex, TypeScript and Use class components in your options.

Creating the store

Let's now create the Vuex store. The store will consist of a singular state which will contain the main count from where we'll derive the incremented and decremented ones.

src/store/index.ts

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});

Getters

Using the count variable in the state we will use getters to fetch the current count, the incremented count and the decremented count. Before we do that though, we'll first create a type for our state so that
we can explicitly type out the arguments required for our getters.

src/types.ts

export interface StateType {
  count: number;
}

src/store/index.ts
Now we can use this type to create our getters.

import Vue from "vue";
import Vuex from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {},
  actions: {},
  modules: {},
});

Mutations and Actions

Now let's create some simple mutations to mutate the count variable of the state. This will cause
nextCount and previousCount to update accordingly.

src/store/index.ts

import Vue from "vue";
import Vuex from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {
    increment(state: StateType): void {
      state.count++;
    },
    decrement(state: StateType): void {
      state.count--;
    },
  },
  actions: {},
  modules: {},
});

Here we are returning void because apart from mutating the count value we are not returning anything.
Of course, now we need to run these mutations so lets create some actions for that.

src/store/index.ts

import Vue from "vue";
import Vuex, { ActionContext } from "vuex";
import { StateType } from "@/types";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    currentCount(state: StateType): number {
      return state.count;
    },
    previousCount(state: StateType): number {
      return state.count - 1;
    },
    nextCount(state: StateType): number {
      return state.count + 1;
    },
  },
  mutations: {
    increment(state: StateType): void {
      state.count++;
    },
    decrement(state: StateType): void {
      state.count--;
    },
  },
  actions: {
    increment(ctx: ActionContext<StateType, StateType>): void {
      ctx.commit("increment");
    },
    decrement(ctx: ActionContext<StateType, StateType>): void {
      ctx.commit("decrement");
    },
  },
  modules: {},
});

Alrighty, now we're done with the store and we can move onto using these little bits of state in our UI!

Using the store in our component

I have a created a component called Counter and set it up like this:

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; 0</button>
      1
      <button>&gt; 2</button>
    </span>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class Counter extends Vue {}
</script>

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Now normally to access our store we would so something like:

this.$store.count; // etc..

But Vuex's TypeScript support is kinda jank and it doesn't work well with class components. So we will have to add a library called vuex-class to use our store in our component.

yarn add vuex-class

or

npm install vuex-class

So the way vuex-class works is you have an associated decorator for a getter, mutation etc. and we pass
that decorator to a variable with the same name as the name of the mutation or getter in the store. For example the way we would call our currentCount getter is:

src/components/Counter.vue

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
}
</script>

And we can call this currentCount property in our template.

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; 0</button>
      {{ currentCount }}
      <button>&gt; 2</button>
    </span>
  </div>
</template>

Now we can do the same for the other getters:

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button>&lt; {{ previousCount }}</button>
      {{ currentCount }}
      <button>&gt; {{ nextCount }}</button>
    </span>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
  @Getter previousCount!: number;
  @Getter nextCount!: number;
}
</script>

We can use the same syntax to include our actions using @Action. Then we will be able to use it as
the buttons' @click handlers.

src/components/Counter.vue

<template>
  <div>
    <h1>vue counter</h1>
    <span>
      <button @click="decrement">&lt; {{ previousCount }}</button>
      {{ currentCount }}
      <button @click="increment">&gt; {{ nextCount }}</button>
    </span>
  </div>
</template>

<script lang="ts">
import { StateType } from "@/types";
import { Component, Vue } from "vue-property-decorator";
import { ActionContext } from "vuex";
import { Getter, Action } from "vuex-class";

@Component
export default class Counter extends Vue {
  // getters
  @Getter currentCount!: number;
  @Getter previousCount!: number;
  @Getter nextCount!: number;

  // actions
  @Action increment!: ActionContext<StateType, StateType>;
  @Action decrement!: ActionContext<StateType, StateType>;
}
</script>

And that's it! You can use the same procedure to use them in bigger/more complex stores too! vuex-class also has support for modules and you can use them with namespaces.

I'll catch you guys in my next post!

34