Vuex and Typescript

Francesco Vitullo
codeburst
Published in
6 min readFeb 4, 2018

--

Lately, Typescript is becoming more popular in the Javascript ecosystem and, by this post, I don’t want to dive deeply into Typescript but I would like to show a basic approach to integrate Vuex within a Vue application with a Typescript codebase.

At this point, I assume you are familiar with basic Typescript approaches and how to use the language in a Vue application. In case you’d like to have a look at a basic TS example, I suggest to check this repo out: https://github.com/Microsoft/TypeScript-Vue-Starter

According to the official documentation, Vuex is defined this way:

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

Credits to the Vuex/Vue contributors and taken from https://vuex.vuejs.org/en/intro.html

Since I have an extensive experience with Flux and Redux, this concept didn’t sound new to me, so if you are familiar with this pattern, it should not be a big deal to get it and start using Vuex.

In my humble opinion, this pattern is really helpful when dealing with applications which need to scale and boosting overall productivity.

Back to the point, how we combine Vuex with Typescript?

First of all, let’s initialise and expose the store in index.ts:

// index.tsimport Vue from 'vue';
import Vuex, { StoreOptions } from 'vuex';
import { RootState } from './types';
import { profile } from './profile/index';

Vue.use(Vuex);

const store: StoreOptions<RootState> = {
state: {
version: '1.0.0' // a simple property
},
modules: {
profile
}
};

export default new Vuex.Store<RootState>(store);

And types.ts:

// types.tsexport interface RootState {
version: string;
}

This code is pretty similar to the standard approach to create a Vuex store but you can notice few differences here:

  • a storeOpts variable is being created with a “StoreOptions” type and defining the generic type to “RootState” (which defines the root state type)
  • new Vuex.Store is using RootState type as well

Thanks to these differences, we are explicitly defining the types for the root Vuex instance.

As usual, I suggest and recommend to embrace a modular approach because there are many advantages while connecting Vuex to multiple components, so I laid down the store with a simple and basic module: Profile.

// profile/index.tsimport { Module } from 'vuex';
import { getters } from './getters';
import { actions } from './actions';
import { mutations } from './mutations';
import { ProfileState } from './types';
import { RootState } from '../types';

export const state: ProfileState = {
user: undefined,
error: false
};

const namespaced: boolean = true;

export const profile: Module<ProfileState, RootState> = {
namespaced,
state,
getters,
actions,
mutations
};

With types.ts:

// types.tsexport interface User {
firstName: string;
lastName: string;
email: string;
phone?: string;
}

export interface ProfileState {
user?: User;
error: boolean;
}

Having a look at the index.ts file, you might notice couple of things:

  • State is being initialised with the ProfileState type
  • Created and exported module is a bit more complex at this stage: it’s a Module defining both types: ProfileState (which is the module state) and the RootState (root state for the Vuex store)

Module is a type declared by Vuex:

// vuex/types/index.d.tsexport interface Module<S, R> {
namespaced?: boolean;
state?: S | (() => S);
getters?: GetterTree<S, R>;
actions?: ActionTree<S, R>;
mutations?: MutationTree<S>;
modules?: ModuleTree<R>;
}

Having a look at the exposed type, Module is a simple object aggregating (optionally) the passed actions/mutations/getters/state and inner modular strategies.

Let’s inspect the Actions in the example.

Actions.ts:

// profile/actions.tsimport { ActionTree } from 'vuex';
import axios from 'axios';
import { ProfileState, User } from './types';
import { RootState } from '../types';


export const actions: ActionTree<ProfileState, RootState> = {
fetchData({ commit }): any {
axios({
url: 'https://....'
}).then((response) => {
const payload: User = response && response.data;
commit('profileLoaded', payload);
}, (error) => {
console.log(error);
commit('profileError');
});
}
};

In order to export something expected by the Vuex’ Module type, we need to aggregate our actions into an “ActionTree”, a type defined in Vuex like the following:

// vuex/types/index.d.tsexport interface ActionTree<S, R> {
[key: string]: Action<S, R>;
}

Not a big deal to understand, it represents an object expecting some keys, defining the name of the action, and an Action associated to (still expecting the Module State and Root State types)

In our case, we have just an ActionTree containing just a simple action named “fetchData”, which performs an async task (retrieving some data from a service) and commits success or error based on the network response. Payload, in case of success, is typed to User.

Let’s have a look at the mutations:

Mutations.ts:

// profile/mutations.tsimport { MutationTree } from 'vuex';
import { ProfileState, User } from './types';

export const mutations: MutationTree<ProfileState> = {
profileLoaded(state, payload: User) {
state.error = false;
state.user = payload;
},
profileError(state) {
state.error = true;
state.user = undefined;
}
};

Mutations are following the same approach we discussed for Actions and expecting a variable of MutationTree type defined by Vuex like the following way:

// vuex/types/index.d.tsexport interface MutationTree<S> {
[key: string]: Mutation<S>;
}

To close the module’s initialisation, we are exposing the needed getters as well. In our case, a simple getter returning the full name of the selected user might be enough, combining the stored firstName and lastName properties.

Yes, you could even do that with a class for the User but I wanted to have a basic example for the getters too.

Getters.ts:

// profile/getters.tsimport { GetterTree } from 'vuex';
import { ProfileState } from './types';
import { RootState } from '../types';

export const getters: GetterTree<ProfileState, RootState> = {
fullName(state): string {
const { user } = state;
const firstName = (user && user.firstName) || '';
const lastName = (user && user.lastName) || '';
return `${firstName} ${lastName}`;
}
};

Defined by Vuex like the following:

// vuex/types/index.d.tsexport interface GetterTree<S, R> {
[key: string]: Getter<S, R>;
}

Now, the interesting part: how do we connect everything to a Vue component?

For the following example, I am using vuex-class to connect a simple component to Vuex.

<template>
<div class="container">
<div v-if="profile.user">
<p>
Full name: {{ fullName }}
</p>
<p>
Email: {{ email }}
</p>
</div>
<div v-if="profile.error">
Oops an error occured
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue';
import { State, Action, Getter } from 'vuex-class';
import Component from 'vue-class-component';
import { ProfileState, User } from './store/profile/types';
const namespace: string = 'profile';
@Component
export default class UserDetail extends Vue {
@State('profile') profile: ProfileState;
@Action('fetchData', { namespace }) fetchData: any;
@Getter('fullName', { namespace }) fullName: string;

mounted() {
// fetching data as soon as the component's been mounted
this.fetchData();
}

// computed variable based on user's email
get email() {
const user = this.profile && this.profile.user;
return (user && user.email) || '';
}
}
</script>

The example above is a really basic one. A single file component containing the “template” ( with a rough strategy to display the right section when the defined conditional turns logically to true) and the “script” exposing our component. In the example, I am using also vue-class-component to use the class-base Vue components (also a dependency for vuex-class).

Thanks to Vuex-class, it’s possible to use decorators to get whatever we need: State, Actions, Mutations, Getters and wrap “namespaced decorators”.

Our component is going to have a couple of computed variables, one named “profile” referring to the Profile’s state and the other one referring to the “getter” we defined in the module.
This example is using two explicit decorators exposed by vuex-class: State and Getter. In order to access to the right module, an object (or BindingOptions) with “namespace” as property is being passed as second argument.

@State('profile') profile: ProfileState;
@Getter('fullName', { namespace }) fullName: string;

In our case, we need to wire the action “fetchData” in with the Action decorator:

@Action('fetchData', { namespace }) fetchData: any;

and execute it in the “mounted” lifecycle’s callback:

mounted() {
// fetching data as soon as the component's been mounted
this.fetchData();
}

To render something meaningful, part of the template is using the previously inspected getter to render the “fullName” and a basic computed property to get the user’s email.

<p>
Full name: {{ fullName }}
</p>
<p>
Email: {{ email }}
</p>

Basically that’s it. There are other ways to connect a Vue component with Vuex but I believe this is a valid way to get started with.

Of course, there is lots of room for improvement in the given example/code, e.g. enhancing the typing of the code to get a more robust logic or better way to render the module’s changes.

I hope you enjoyed this article!

--

--

Senior Front End Engineer @IBM — thoughts are my own / I’m a passionate developer and kitesurfer