Vuex store type with Typescript
Asked Answered
Y

4

18

I'm trying to get a Vuex store to be Typescript friendly. I'm building the store as explained here. However, when I access this.$store from a component, the type is Store<any> .

I couldn't figure out how to change it so that it defaults to Store<MyState> without requiring a cast every time.

Yungyunick answered 4/11, 2018 at 10:45 Comment(0)
C
5

If anyone comes across this - we got around this issue by redefining the type that the constructor returned -

import Vue, { VueConstructor } from 'vue'
import { Store } from 'vuex'
import { RootState } from '@/store/types'

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

export default VueStrong;

and then we just

export default VueStrong.extend({
    name: 'name',

    components: {
        componentA,
        componentB,
},

which lets us then use typing properly:

methods: {
sessionStarted(): Boolean | undefined {
    return this.$store.state.sessionState?.session.started;
},
Crystalloid answered 4/3, 2020 at 22:8 Comment(2)
do you know if there's a way to get typing to work with this.$store.getters?Portend
@Portend Unfortunately I don't think so - you might be able to get real fancy with an enum and some generics, but that feels worse than just casting manually. =\Crystalloid
A
7

Unfortunately it is impossible to override the Store<any> type that is defined by the VueX types with a more specific type. The best I could come up with was to add a second field that returns $store but properly typed so you don't have to use casts everywhere or declare it in all your components:

import { Store } from "vuex";

// Type the $tstore properly, which is the same as $store but properly typed.
// Unfortunately you cannot override the Store<any> type.
declare module "vue/types/vue" {
  interface Vue {
    $tstore: Store<State>;
  }
}

// Set $tstore to be a getter that simply returns $store.
Object.defineProperty(Vue.prototype, "$tstore", {
  get: function() {
    return this.$store as Store<State>;
  },
  enumerable: true,
});
Airlike answered 28/11, 2019 at 16:1 Comment(1)
You can improve on this, when you add /** @deprecated Use $tstore instead! */ $store: Store<State> to the declaration. At least in my IntelliJ, every use of this.$store is then marked as deprecated.Evanthe
C
5

If anyone comes across this - we got around this issue by redefining the type that the constructor returned -

import Vue, { VueConstructor } from 'vue'
import { Store } from 'vuex'
import { RootState } from '@/store/types'

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

export default VueStrong;

and then we just

export default VueStrong.extend({
    name: 'name',

    components: {
        componentA,
        componentB,
},

which lets us then use typing properly:

methods: {
sessionStarted(): Boolean | undefined {
    return this.$store.state.sessionState?.session.started;
},
Crystalloid answered 4/3, 2020 at 22:8 Comment(2)
do you know if there's a way to get typing to work with this.$store.getters?Portend
@Portend Unfortunately I don't think so - you might be able to get real fancy with an enum and some generics, but that feels worse than just casting manually. =\Crystalloid
S
4

You could declare a property in your component so typescript will apply typing. I use this for $refs all the time but it works for the $store too. You don't need to do anything with the property apart from marking it with ! operator to tell the transpiler that vue will set the variable for you.

$store!: Store<StoreStateType>;

Another alternative I use is to use the MapState or MapGetters component helpers. They create the properties for you so you can use them in the template. Example:

@Component({
  computed: mapState({
    userFullName: (state: any) => state.user.fullName,
    userEmail: (state: any) => state.user.email
  })
})

Don't forget to import the Store, your vuex state class and any helper you use import { Store, mapState } from "vuex";.

Syllable answered 17/2, 2019 at 22:47 Comment(0)
F
0

None of the answers satisfied me. I did some digging and found this little gem. Inside of my store.ts file, I merely add:

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}

So it looks more like this:

//store.ts
import { createStore, Store } from "vuex";
import { CustomTool, CustomListType } from "custom";

export type State = {
  tool: CustomTool
  list: CustomListType | null
};

export default createStore({
  state: {
    tool: CustomTool.Empty
    list: null,
  } as State,
  mutations: {},
  actions: {},
  modules: {}
};

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}

Using vue3 and typescript 4.4.

Fistula answered 21/10, 2021 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.