Properly typed Vuex Stores

Vinicius Teixeira
The Startup
Published in
6 min readMay 13, 2020

TLDR: In this article, we discuss having fully type-checked Vuex stores. Full code is available in vinicius0026/properly-typed-vuex-stores.

Photo by Caspar Camille Rubin on Unsplash

This is the first article in our Structuring Large Vue.js Applications series. Here is the full list of released and planned articles:

Vue.js and TypeScript are two trending topics in front-end development.

Vue.js calls itself “The Progressive JavaScript Framework”, meaning by that that the entry barrier to start using it is very low, but as one’s needs grow, Vue’s capabilities and its ecosystem will be there to match those needs.

One of the common patterns mid-sized to large applications need is centralized state management. Whenever your Vue app needs that, the no-brainer choice is Vuex, Vue’s own implementation of “State Management Pattern”.

When apps start to grow beyond mid-size, TypeScript starts to be more and more valuable to manage complexity.

This article discusses ways of putting all this together, to have a solid base for a large application using Vue, Vuex, and TypeScript.

Default Setup

Thanks to the fantastic vue-cli tool, it is straightforward to start a Vue + Vuex + TypeScript project.

After installing vue-cli, just run vue create <project name>, answer a few questions and you will be up and running. Make sure to select Manually select features in the Please pick a preset stage and check TypeScript and Vuex.

vue-cli options for scaffolding a new app

When vue-cli finishes scaffolding the app and installing all dependencies, we will have a Vuex store stubbed out for us, in TypeScript:

Generated Vuex Store

For large-sized app, we should break our store into modules to make it more manageable. Let’s create an auth module, where we will keep track of the authenticated user in our app.

# pwd is the root fo the generated app
$ mkdir src/store/modules
$ touch src/store/modules/auth.ts

And then let’s create the Auth Store:

Auth store module initial implementation

Here we are defining simplistic User interface, with just a name; an AuthState interface to use as the type for our store's state; and a getter and a mutation to get and set the user.

We can now register this module in the root store:

Registering the auth module in the main store

With that wired up, we can use the auth store in our app. Let's add a login link that will eventually call our auth store to set the authUser to a fake user.

Adding a login button to our App.vue file

Here we have a couple of options when it comes to accessing our store from a component. We can either use the $store accessor in the Vue component object or use Vuex helpers such as mapGetters and mapMutations. Let's try both.

Using this.$store is pretty straightforward:

Accessing the store using this.$store

Using the mapMutations helper is just a tiny bit more involved:

Accessing the store using mapMutations helper

Both work fine, as we can check by using vue-devtools:

Using vue-devtools to check that our mutation works

But neither is a great experience with regards to type checking. Although we have added types to all the parameters in our store, we don’t get type-checking for the store mutation.

Empty type hints for setUser imported using mapMutations
Using this.$store provides slightly better hints, but still not good enough

And in fact, they can lead to a bad state to be committed to the store:

Bad state not caught by typechecking

But there is a Better Way™.

A Better Way™

To have our store functions properly type-checked, we will use vuex-typex, a nice but oddly still little used library.

$ npm install -S vuex-typex

We will have to write our store in a slightly different way, but it will pay off by enabling type-checking across our whole app.

First of all, we will have to create a new file, where we will declare our RootState interface, and we will create our storeBuilder object from vuex-typex, based on our RootState.

RootState definition

Now, let’s rewrite our auth module to use vuex-typex:

Using vuex-typex to create our auth store module

There’s a lot going on here. First, we have changed our AuthState declaration from a typescript interface to a class. Then, we are using this class to instantiate a module builder (that we call just b for ease of access). Notice how we are passing the AuthState as a type parameter to the storeBuilder.module call, and also using it to instantiate our initial state, by calling new AuthState().

Using the module builder, we can now declare our getter and our mutation. We use b.read to declare getters and b.commit to call mutations. Instead of exporting a default object with the state, getters, mutations, and actions, we individually export the state, getters, and mutations.

Now we need to change the store’s index file to use vuex-typex:

Updating index.ts store file to use the storeBuilder from vuex-typex

It got actually simpler than before. One caveat here is that we have to make sure the modules are evaluated before we import the storeBuilder from the RootState file. We achieve that by an anonymous import (line 4 in the code above).

We now have to update the component’s code. Instead of using this.$store or mapMutations, we are going to import the auth store module directly.

Accessing the store via the Auth store module directly

And now we have type-checking in our store!

Type hints for the store mutation in the controller after we start using vuex-typex

And the type-checker will warn us of any mismatch on the types.

VSCode hints for a type error
Typescript check error

Getters

Getters are also type-checked as we can confirm by accessing the authUser:

Proper hints for the authUser getter

Notice the use of the optional chaining operator ?. If we remove it, we will have a type error, because the authUser is possibly null.

Appropriate type error message when trying to access a possibly null object

Actions

Declaring actions in our type-checked store requires just a bit more effort than getters and mutations. We need to create a new type for the action context, like so:

Adding an action to the store

Here we declare a login action that, like any other Vuex action, takes the context as the first argument. In this case, we use a special type for the context to be able to access, if needed, the current module state or global state inside the action.

The second parameter to the login action is whatever we need to perform this action. Here we are using an object to pass in a username and a password, but it can be anything.

We defined a fake user service to represent the async call that would eventually return the user if the login is successful, and then we are calling the setUser mutation.

To wire this action up to the store, we use the module builder dispatch method (b.dispatch).

We can now update our component to use this action to log the user in:

Calling the store action from our component

We now have a fully type-checked store!

Conclusion

vue-cli provides an effortless way to get up and running with a standard Vue application layout. But, unfortunately, Vuex doesn't have full support for TypeScript out of the box.

We are getting close to Vue 3 launch, and it will have native support for TypeScript. Vuex 4.0 (that will be the vuex version compatible with Vue 3) is still in Beta at the time of this writing, and it still doesn’t provide full type-checking for Vuex stores declared with TypeScript. We hope to see improvements in type-checking as it gets closer to be released.

Anyway, for all the Vue 2.* applications out there, this article should help to get TypeScript’s full power to Vuex Stores.

Originally published at https://viniciusteixeira.tk on May 13, 2020.

--

--