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:
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
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
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.
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
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:
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.
Top comments (2)
Good tip, work well.
We adapted this trick to pass the store type through a generic like this, so that it can work in a multi store environment:
A few years late - but I'm glad this helped you!!!