TLDR: In this article, we discuss having fully type-checked Vuex stores. Full code is available in vinicius0026/properly-typed-vuex-stores.
This is the first article in our Structuring Large Vue.js Applications series. Here is the full list of released and planned articles:
- Properly typed Vuex Stores — published May 13, 2020 — You are here
- Adopting TypeScript in your Vue.js Application in a sane way — published May 14, 2020
- Modularizing the logic of your Vue.js Application — published May 15, 2020
- Data-driven components — published May 25, 2020
- Handling data at the edge of your Vue.js application — published June 1st, 2020
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.
Thanks to the fantastic
vue-cli tool, it is straightforward to start a Vue + Vuex + TypeScript project.
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
vue-cli finishes scaffolding the app and installing all dependencies, we will have a Vuex store stubbed out for us, in TypeScript:
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:
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:
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.
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
mapMutations. Let's try both.
this.$store is pretty straightforward:
mapMutations helper is just a tiny bit more involved:
Both work fine, as we can check by using
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.
And in fact, they can lead to a bad state to be committed to the store:
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
Now, let’s rewrite our
auth module to use
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
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
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
mapMutations, we are going to import the
auth store module directly.
And now we have type-checking in our store!
And the type-checker will warn us of any mismatch on the types.
Getters are also type-checked as we can confirm by accessing the
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:
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
To wire this action up to the store, we use the module builder
dispatch method (
We can now update our component to use this action to log the user in:
We now have a fully type-checked store!
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.
Shameless Plug: If you liked this article, I’m currently looking for a job as a Senior Full Stack Engineer. You can check my Linkedin and drop me a line at vinicius0026 at gmail dot com if you think I’m a good fit for some position at your company. Cheers! 😃
Originally published at https://viniciusteixeira.tk on May 13, 2020.