Strongly typed VueX store in four lines of TypeScript
Tags: typescript, vue, vuex
Published: 2020-03-06 02:04:56

#The issue
I was lucky enough to get to work on a Vue/TypeScript project at work recently, and a common issue developers complained about was the lack of typing in the store.  

Typically our files looked like this, and threw no type errors:

```typescript
import Vue from 'vue';

export default Vue.extend({
    name: 'whatever',
    computed: {
        getVariable(): string | undefined {
            return this.$store.state.LITERALLY.ANY.THING;
        },
    },
});
```

This works, but it would be nice to utilize all of the state interfaces that we defined for use with VueX. 

#The existing definitions

Looking at the Vuex definition file, `$store` has a type of `Store<any>`.

*vuex/types/vue.d.ts*

```typescript
declare module "vue/types/vue" {
  interface Vue {
    $store: Store<any>;
  }
}
```

The plan here is to change the type `Store<any>` into `Store<RootStore>`.

There is one issue though, just amending this definition will not be enough. We use `Vue.extend()` to create our components, and the type of `Vue` instance returned from that method does not contain our custom `$store` definition. 

Looking at the original `Vue` definition, all of the `.extend` methods include the `V` type variable in the return type, which is `Vue` by default.    

*vue/types/vue.d.ts*
```typescript
export interface VueConstructor<V extends Vue = Vue> {
...
  extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;
...
}
```

#The solution

If we pass a different type variable into `VueConstructor`, that should return the properly typed store instance.

*Note: `RootState` is the interface we created to represent our root state. I don't believe this is very uncommon, but it's included for reference.*

```typescript
export interface RootState {
    loaded: Boolean
}
```

In this example, I'm using the name `StrongVue` to represent Vue being strongly typed.  Super clever. 

I created this file in it's own folder, and since it'll be used from now on instead of the standard `Vue` object, I put it somewhere easily accessible.

*strong-vue/strong-vue.ts*
```typescript
import Vue, { VueConstructor } from 'vue'
import { Store } from 'vuex'
import { RootState } from '../store/types'

abstract class StrongVueClass extends Vue {
	public $store!: Store<RootState>;
}
const StrongVue = Vue as VueConstructor<StrongVueClass>;

export default StrongVue;
```

We create an abstract class that extends `Vue`, and redefines the `$store` type from `Store<any>`to `Store<RootState>`.  The final step is to cast the imported standard `Vue` object to our new `VueConstructor<StrongVueClass>`, and export that. 

So now, instead of importing `Vue` directly - we use our custom definition:
```typescript
import StrongVue from '../strong-vue/strong-vue'

StrongVue.extend({
    computed: {
        getVariable(): boolean {
            return this.$store.state.LITERALLY.ANY.THING; //will throw type error
            return this.$store.state.loaded; //gives no type error
        },
    },
})
```

I haven't found anything online about this solution, and it's been pretty reliable so far, as long as you keep your `RootState` up to date.  

Thanks.