vuejs/vuex

Write Stores in Typescript

Anonyfox opened this issue Β· 88 comments

I couldn't find anything on google, so is this conveniently possible? Basically the bummer for me is that I have to call dispatch/commit with a given identifier string to trigger something.

My vision is to define stores as Typescript classes with typed methods (getting intellisense in editor and so on) which I can call instead of dispatch("my_action", data) and having to look up each and everything and check for errors manually all the time.

Basically my problem is that my team is building a fairly large Vue/Vuex frontend for a core product of our company, consisting of already 18 fairly complex Stores as modules, and this year stuff is going to at least quadruple in size and complexity. We already decided to go on with typescript instead of ES7 and are in the process of migrating our backend/frontend code, but this thing really feels like a bummer.

I think this is not the typical "small" use case for Vue.js, since we're building a huge enterprise frontend with loads of requirements and edge cases, but isn't vuex(/flux) supposed to scale up when complexity rises?

Has anyone experience in building complex, type safe Vuex 2.0 stores in Typescript yet? Any help here would be appreciated

/ping @ktsn

ktsn commented

Thank you for trying Vuex with TypeScript.
Honestly, it is challenge to achieve type safety with pure Vuex API. But we currently can specify action/mutation type as type parameter to let TS compiler can infer the correct type.

// Declare each action type
type ActionA = {
  type: 'A',
  value: number
}

type ActionB = {
  type: 'B',
  value: string
}

// Declare the union type of actions
type Action = ActionA | ActionB

// Provide action type
store.dispatch<Action>({
  type: 'A',
  value: 1
})

About defining the store as class, I believe we can make such binding like vue-class-component and I personally interested in it. Also, there is an experiment for type safety of Vuex. https://github.com/HerringtonDarkholme/kilimanjaro

Mh, okay, this might be a start. For better clarification, I'll provide a simplified example for a store that will later get included as a module:

state.ts:

export class State {
    // persons above 24 years old
    adults: number = 2

    // persons between 16 and 24 years old
    juveniles: number = 0

    // persons below 16 years old
    children: number = 0
}

mutations.ts:

import { State } from './state'

export type personIdentifier = 'adults' | 'juveniles' | 'children'

export class Mutations {
    // increment the chosen persons type by one
    inc (state: State, key: personIdentifier) {
        state[key]++
    }

    // decrement the chosen persons type by one
    dec (state: State, key: personIdentifier) {
        state[key]--
    }
}

actions.ts:

import { Store } from 'vuex'
import { State } from './state'

export class Actions {
    inc ({ commit }: Store<State>) {
        // ??????
    }
}

skipping the getters for now, since they're unimportant. This is where I am currently, and I would combine these classes into a Store with namespaced: true, since this store might be used on multiple places independently for several UI components.

Is there a solution how I might write these actions as type safe methods? Your example is not clear enough for me to apply the pattern you provided, I think.

ktsn commented

Hm, there maybe no solution when namespaced: true is used since we cannot combine string literal type 😞
I guess we need some wrapper to trick the type checking.

small heads-up: it was really straightforward to implement a Store-Module as a standalone npm package in typescript, with tooling/tests/typings and namespaced:true, and then use it dynamically within the core app.

Thanks to the recent efforts of you guys for the typescript support, implementing the interfaces really helped to get it right immediately!

In my opinion the only puzzle-piece left is typesafe commit/dispatch (with namespaced), but I think this is a hard problem to bolt-on vuex as-is. One idea would be to generate types on the "new Vuex.Store" call in the main code, which map the generated identifier-string to the underlying function or sth like that. But ideally, there could be an alternate way to call commit/dispatch, maybe through wrapper classes to Module that does the actual calling behind the scenes or something. This seems to be an architectural issue, which is really not easy to resolve.

On the other hand I think it would really, really great to solve this. Even if approx. most vuex users do "just" use ES7, they could benefit from intellisense/hints from their Editors/IDEs greatly, providing huge ergonomic value once stores become nontrivial in size.

morhi commented

@Anonyfox Would you mind creating an example repository or snippet for vuex with typescript, modules and classes as far as you got it so far? I am currently setting up a vue project which probably will contain a few store modules. I understand using vuex with ES6 but I am struggling with properly setting up my project with typescript. I would like to have everything structured well but it seems a bit complicated at the moment :)

As for now I created two global files (mutations.ts and actions.ts) where I export some constants of the mutation types and actions that all stores use. These files are imported both in the store modules and in the components that use the store so I don't need to use string as identifiers.

@morhi sure, I extracted a simple (but not trivial) store we're using.

https://github.com/Anonyfox/vuex-store-module-example

please respect that I can not give a valid license, this is just intended for demonstrating.

/cc @ktsn

I think it would be nice if we can use namespaced module like as

/*
 * equivalent to ctx.commit("foo/bar/action", arg)
 */
ctx.commit.foo.bar("action", arg);
// OR
ctx.foo.bar.commit("action", arg);
// OR
ctx.modules.foo.bar.commit("action", arg);

/*
 * equivalent to ctx.getters("foo/bar/prop")
 */
ctx.getters.foo.bar.prop
// OR
ctx.foo.bar.getters.prop
// OR
ctx.modules.foo.bar.getters.prop

This will make adding and combining types more easy.

morhi commented

@Anonyfox wow! thank you very much! I adopted your example into my environment and got it working πŸ‘

Related: #532

One way to ensure type consnistency between dispatch/commit payload vs. action/mutation handler, is to declare both of them at once. Eg.

http://tinyurl.com/go9ap5u

However, you'd need some sort of runtime decoration approach to setup necessary pre-initializations prior to app running. Also, Typescript doesn't seem to complain if I override a method with a different payload type parameter form, which is something that i'd need to enforce, though (anyway to do this?). Overall, setting up all the decorators and custom initilizations at runtime is a lot of work.

FYI, I wrote helper to make store half-decent type safe.
https://gist.github.com/wonderful-panda/46c072497f8731a2bde28da40e9ea2d7

It seems to work well except namespaced module.

@wonderful-panda Is it possible with your code to add modules within modules recursively?

@Glidias yes.

Nested module example is below:

const module1 = builder.createModule<...>({ /* module1 definition */ });
const module2 = builder.createModule<...>({ /* module2 definition */ });

const parentModule = 
    builder.addModule("bar", module1)
           .addModule("baz", module2)    // { modules: { bar: module1, baz: module2 } }
           .createModule<...>({ /* parentModule definition without `modules` */ });

const store =
    builder.addModule("foo", parentModule)
           .createStore<...>({ /* store options without `modules` */ });

@wonderful-panda See the comments in the gist for continued discussion in that area.

Regarding dynamic namespacing of modules + strict typing, one idea i was considering is that after a module tree is initialized, you traverse through it's state tree and will always set a _ property under each module state within the tree (including root store state for the sake of homogeneity), in order to have their namespace path references stored dynamically at runtime. That way, you can simply use rootState.moduleA.moduleB._, to retrieve a path accordingly. Or if the context is unknown within actions (may/may not be root), you can use context.state._. However, this will require you to strictly hardcode the module interface field references (under each State for typehinting) if you're not using the builder utility. Also, a _ property reference must be set up as well per module state to ensure you get typehinting/type-completion. The Builder already sets up combined state for module references under a given parent state, so your Module state would probably just implement a dummy IPrefixedState interface that provides an optional readonly _?:string parameter that will give you boilerplate typehinting.

For strictly typed dispatches of module-specific mutations/actions, one way is to adopt a generic helper wrapper method to commit something through a given context (with/without namespace prefixing..)

https://github.com/Glidias/vuex-store-module-example/blob/master/src/util/vuexhelpers.ts

However, this will result in a bit of additional performance overhead of calling the wrapper helper function (remember, there's no inlining in Typescript). But i guess, it's okay and shouldn't be too much of an issue.

 import { MutationTypes } from './mutations';

import * as VuexHelper from "./util/vuexhelpers"
const commitTo = VuexHelper.getCommitToGeneric<MutationTypes>();

// within action handler context (affecting another module somewhere else..)
commitTo(context,"INC", "adults", {root:true}, context.rootState.someModule.anotherNestedModule._)

  // if a particular set of action handlers aren't aware if it's registered under a namespaced:true module or not
  commitTo(context,"INC", "adults", {root:true}, context.state._)

 // or something like this within a Component context:
commitTo(this.$store,"INC", "adults", undefined, $store.state.someModule.anotherNestedModule._)

Also, if your module needs to respond to both namespaced vs non-namespaced mutations/actions, namespaced:true vuex setting won't work well anyway. So, here's a possible approach in userland: https://github.com/Glidias/vuex-store-module-example/wiki/Managing-mixed-namespacings-between-module's-mutations-and-actions

I've done some work towards this end and the solution is coming along nicely. I've created a set of decorators similar to vue-class-component, which combine with a few conventions to make a pretty nice experience in TS. It's currently light on documentation (like, none heh) but the tests tell the story pretty well, I hope... https://github.com/snaptopixel/vuex-ts-decorators/blob/master/test/index.ts

I've invited @ktsn and @yyx990803 as collaborators but happy to receive ideas and pull requests from anyone. I'll be writing some docs and examples asap.

ktsn commented

Thank you very much for all of your works!
I personally investigating this topic recently and created an experimental package of Vuex like store library. It actually does not Vuex but I believe the mechanism can help us to achieve Vuex's type safety somehow πŸ™‚

Nice @ktsn I'll take a look at your work, thanks for the heads up! Give mine a look too as you find time.

@snaptopixel I just tried out your decorators and I think they are pretty great!

Very simple to write and use. Defining stores isn't too different from normal Vuex and using them is exactly the same as normal Vuex except the added type safety (including with commit and dispatch!) which is nice!

However there are a few things that I could not get to work, so I added some questions/issues to your repository.

Awesome @chanon thank you so much for trying them out and filing issues. That helps tremendously. I'm planning on adding a proper readme soon and will definitely be working on the issues posted.

My team was looking for a simple, tiny and unobtrusive solution. After several iterations I distilled it to this: https://github.com/istrib/vuex-typescript.
Using wrapper functions as a proxy to store.dispatch/commit/getters seemed most natural and higher-order functions with TypeScript type inference removed most boilerplate.
I specifically DID NOT want to use classes (there is nothing to encapsulate). Using ES6 modules to group actions/getters/mutations of the same Vuex module provides enough structure IMO.

//Vuex Module (like in JS + type annotations, no classes):

export const basket = {
    namespaced: true,

    mutations: {
        appendItem(state: BasketState, item: { product: Product; atTheEnd: boolean }) {
            state.items.push({ product: item.product, isSelected: false });
        },
    ...

//Strongly-typed wrappers:

const { commit } =
     getStoreAccessors<BasketState, RootState>("basket"); // Pass namespace here, if we make the module namespaced: true.

// commit is a higher-order function which gets handler function as argument
// and returns a strongly-typed "accessor" function which internally calls the standard store.commit() method.
// Implementation of commit is trivial: https://github.com/istrib/vuex-typescript/blob/master/src/index.ts#L103

// you get intellisense here:
export const commitAppendItem = commit(basket.mutations.appendItem);

// commitAppendItem is a function with strongly-typed signature
// so you get intellisense for function name and types of its arguments here too:

import * as basket from "./store/basket";
basket.commitAppendItem(this.$store, newItem);

@istrib I really like what you've done in vuex-typescript.

I just wanted to take it a bit furtherβ€”if you don't agree these ideas, that's totally okay. If you do agree, I'd be keen to incorporate these changes into vuex-typescript somehow.

My main changes are:

  • Avoid passing $store/context to the accessor methods: we can encapsulate these within the accessors by providing the store later:
    i.e. basket.commitAppendItem(newItem) should be sufficient.
  • No need to distinguish between payload / payload-less versions of commit + dispatch.
    Typescript overloads solve this problem.
  • Promises returned from dispatch should be strongly-typed.
  • Assumes namespaced modules

I also took the point of view that we don't need to start with a vuex-store options object. If we treat the accessor-creator as a builder, then the store can be generated:

import { getStoreBuilder } from "vuex-typex"
import Vuex, { Store, ActionContext } from "vuex"
import Vue from "vue"
const delay = (duration: number) => new Promise((c, e) => setTimeout(c, duration))

Vue.use(Vuex)

export interface RootState { basket: BasketState }
export interface BasketState { items: Item[] }
export interface Item { id: string, name: string }

const storeBuilder = getStoreBuilder<RootState>()
const moduleBuilder = storeBuilder.module<BasketState>("basket", { items: [] })

namespace basket
{
    const appendItemMutation = (state: BasketState, payload: { item: Item }) => state.items.push(payload.item)
    const delayedAppendAction = async (context: ActionContext<BasketState, RootState>) =>
    {
        await delay(1000)
        basket.commitAppendItem({ item: { id: "abc123", name: "ABC Item" } })
    }

    export const commitAppendItem = moduleBuilder.commit(appendItemMutation)
    export const dispatchDelayedAppend = moduleBuilder.dispatch(delayedAppendAction)
}
export default basket

/// in the main app file
const storeBuilder = getStoreBuilder<RootState>()
new Vue({
    el: '#app',
    template: "....",
    store: storeBuilder.vuexStore()
})

What would be the recommended approach here? We are starting a new project at work and are really keen to have type safety on our stores (including commit/dispatch).

I like your approach, @mrcrowl
I am successfully using a similar builder on one of my current projects. Works really well.

With https://github.com/istrib/vuex-typescript I searched for a solution which produces code that is identical to vanilla Vuex + a bit of simple stuff below it. For that reason I did not mind starting with Vuex options rather than have them built. That is also why I decided to explicitly pass $store into accessors. I like your variation for doing that much while still being tiny.

https://github.com/istrib/vuex-typescript now contains your two great suggestions: making promises returned from dispatch strongly-typed and using function overloads for mutations/actions without payload.

@istrib do you have gist example for reference please?

@Shepless There is a complete example here with this file giving the best overview.

Thanks @istrib for the starting point. I am looking to get Vuex setup with typescript and I just stumbled across vuex-typescript. Unfortunately, I am struggling to get it working correctly. Is there a reference of how the store is actually instantiated?

I am doing:

import * as Vue from 'vue';
import * as Vuex from 'vuex';

// vuex store
import {createStore} from './store';

Vue.use(Vuex);

new Vue({
  el: '#app-main',
  store: createStore()
});

But I am getting the error:

[vuex] must call Vue.use(Vuex) before creating a store instance

My directory structure is pretty close to the example:
https://github.com/istrib/vuex-typescript/tree/master/src/tests/withModules/store

I've done an impl. of the two approach in a Todo app. Here's the repo Vue.js + Vuex (typescript & typex) β€’ TodoMVC


@michaelharrisonroth You're certainly doing

export const createStore = new Vuex.Store<State>({

expect of

`export const createStore = () => new Vuex.Store<State>({`

in your store.ts file

@DevoidCoding That was the issue and your TodoMVC is extremely helpful, thank you!

Since vue 2.5 has released with full typescript supported, I just want to know which is the official implementation of vuex in typescript, vuex-typescript or vuex-typex or something else?. I think having an example in the official page will help.

I just want to know which is the official implementation of vuex in typescript, vuex-typescript or vuex-typex or something else?

The short, probably disappointing answer is: There's not official implementation.

Vue's 2.5 Typescript improvement apply to Vue, not vuex.

@ktsn Do you have a recommendation?

ktsn commented

@LinusBorg @sandangel
I actually haven't had experiences with such wrappers for type safety in Vuex.
But I'm thinking about improving Vuex typings that would work well with Vue v2.5 typings.
The fundamental idea can be seen here. https://github.com/ktsn/vuex-type-helper

how about rxjs powered vuex store with typescript? It would be great if Vuex team can considerate rewriting this library with rxjs and type safe in mind like @ngrx.

rewriting ... with rxjs

if at all, this would only be happening as an optional extension, because rx is far too heavy for an otherwise lightweight library like vuex.

@LinusBorg what i mean when saying "rxjs in mind" is the idea thinking data as a stream. Observable is now at stage 1 in ECMAScript proposals and will be become native javascript soon, which mean you don't have to use rxjs to create observable, you just have to expose Getters, Actions as observable, import some operators and lettable operator in rxjs 5.5 will keep library lightweight. IMO, it 's hard to achieve that goal with current design/architecture of vuex, so a full rewrite is needed.

Somehow I can't get this.$store.state to be of type MyState and not any. Does anyone got it working?

@szwacz it's being discussed here: #994. Because Vuex includes typings for this.$store, you can't overwrite them on a project-basis. You also can't import specific properties from Vuex and define this.$store yourself, because the file that imports Vuex actually does this overwriting.

The only way I could get it working was copying the types included in Vuex in my directory and telling the compiler to use my project's file, not Vuex's. With a tsconfig.json like this:

//tsconfig.json
{
  "compilerOptions": {
      "baseUrl": "./",
      "paths": {
        "vuex": ["typings/vuex.d.ts"]
      }
      ...
   }
}

and a typings/vuex.d.ts file that looks like this: https://gist.github.com/mmitchellgarcia/af540bd0bcb3a734f2c35287584afad3.

You should get Intellisense/static-typing for this.$store.state

It will go out of sync when there's version update; so this is not a long-term solution. I believe this issue will be resolved shortly, so I hope it helps for now!

This is my attempt. Welcome to trial and suggestion.
https://github.com/zetaplus006/vubx

If you are using TypeScript 2.1+, you can take advantage of keyof and mapped types to get type safety for your commit and dispatch calls (both for the string identifiers and their payloads). It only involves types and doesn't require any additional runtime code. Here's a gist that shows the idea.

https://gist.github.com/bmingles/8dc0ddcb87aeb092beb5a12447b10a36

ktsn commented

I just made a PR to improve Vuex typings. #1121
It would be appreciated if you can input any suggestion/comments πŸ™‚

I tried writing better types for Vuex and I thought about making a PR with these changes, but it doesn't really work completely. The problem is on how modules are created, the rootState, rootGetters, and some other concepts of Vuex that just merges isolated objects and makes typing safety impossible. There are few variations of what I did but if you copy this code and paste it on Typescript Playground you will see that without using modules it works perfectly fine. With modules I stepped in the territory of Inferring nested generic types, which turned out to be a real challenge and it doesn't really work (more related to modules tree, see below).

Another thing to note is that the Vue type augmentation performed creates a store as any, which regardless of any work we could do with the typing system would make the $store in components act as any for operations like commit, dispatch, etc. We can't really do that without adding extra parameters on Vue core types, but we don't want to do that because Vue would know about and be couple with Vuex. Maybe we could leave the augmentation to the project level so the specific generics could be passed in? I'm not sure what would be the best way around this, need to research more.

Honestly, at this moment, with my limited knowledge, I would say only rewriting chunks of Vuex would allow us to have decent TS types.

Here is what I got, that might be helpful in case somebody is giving a thought to this:
(I had to copy the basic Options type so it compiles fine, this is just a small set of the types directly related to the Store object)

export interface ModuleOptions{
  preserveState?: boolean
}

export interface WatchOptions {
  deep?: boolean;
  immediate?: boolean;
}

export interface DispatchOptions {
  root?: boolean;
}

export interface CommitOptions {
  silent?: boolean;
  root?: boolean;
}

export interface Payload {
  type: string;
}

export interface Payload {
  type: string;
}

export interface MutationPayload extends Payload {
  payload: any;
}

export interface Dispatch<Actions> {
  <T>(type: keyof Actions, payload?: T, options?: DispatchOptions): Promise<any>;
  <P extends Payload>(payloadWithType: P, options?: DispatchOptions): Promise<any>;
}

export interface Commit<Mutations> {
  <T>(type: keyof Mutations, payload?: T, options?: CommitOptions): void;
  <P extends Payload>(payloadWithType: P, options?: CommitOptions): void;
}

export type Mutation<State> = <Payload>(state: State, payload?: Payload) => any;

export type MutationTree<State, Mutations> = {
  [K in keyof Mutations]: Mutation<State>
}

export type Getter<State, RootState, Getters, RootGetters> = (
  state: State,
  getters: Getters,
  rootState: RootState,
  rootGetters: RootGetters
) => any;

export type GetterTree<State, RootState, Getters> = {
  [K in keyof Getters]: Getter<State, RootState, Getters[K], Getters>
}

export interface ActionContext<State, RootState, Getters, Mutations, Actions> {
  dispatch: Dispatch<Actions>;
  commit: Commit<Mutations>;
  state: State;
  getters: Getters;
  rootState: RootState;
  rootGetters: any;
}

type ActionHandler<State, RootState, Getters, Mutations, Actions> = <Payload>(
  injectee: ActionContext<State, RootState, Getters, Mutations, Actions>,
  payload: Payload
) => any;

interface ActionObject<State, RootState, Getters, Mutations, Actions> {
  root?: boolean;
  handler: ActionHandler<State, RootState, Getters, Mutations, Actions>;
}

export type Action<State, RootState, Getters, Mutations, Actions> =
  | ActionHandler<State, RootState, Getters, Mutations, Actions>
  | ActionObject<State, RootState, Getters, Mutations, Actions>;

export type ActionTree<State, RootState, Getters, Mutations, Actions> = {
  [K in keyof Actions]: Action<State, RootState, Getters, Mutations, Actions>
}

export interface Module<State, Getters, Mutations, Actions, Modules> {
  namespaced?: boolean;
  state?: State | (() => State);
  getters?: GetterTree<State, any, Getters>;
  mutations?: MutationTree<State, Mutations>;
  actions?: ActionTree<State, any, Getters, Mutations, Actions>;
  modules?: ModuleTree<Modules>;
}

export type ModuleTree<Modules> = {
  [K in keyof Modules]: Module<
    Modules[K],
    Modules[K],
    Modules[K],
    Modules[K],
    Modules[K]
  >
}

export type Plugin<State, Mutations, Getters, Actions, Modules> = (store: Store<State, Mutations, Getters, Actions, Modules>) => any;

export interface StoreOptions<State, Mutations, Getters, Actions, Modules> {
  state?: State;
  getters?: GetterTree<State, State, Getters>
  mutations?: MutationTree<State, Mutations>;
  actions?: ActionTree<State, State, Getters, Mutations, Actions>;
  modules?: ModuleTree<Modules>;
  plugins?: Plugin<State, Mutations, Getters, Actions, Modules>[];
  strict?: boolean;
}

class Store<State, Mutations, Getters, Actions, Modules> {
  constructor(options: StoreOptions<State, Mutations, Getters, Actions, Modules>) { 
      
  };

  readonly state: State;
  // trying to add this to test whether we could at least 
  // get the types from a regular modules property
  readonly modules: Modules;
  readonly getters: Getters;

  replaceState: (state: State) => void;

  commit: Commit<Mutations>;
  dispatch: Dispatch<Actions>;

  subscribe: <P extends MutationPayload>(fn: (mutation: P, state: State) => any) => () => void;
  watch: <T>(getter: (state: State) => T, cb: (value: T, oldValue: T) => void, options?: WatchOptions) => () => void;

  registerModule: <
    ModuleState,
    ModuleGetters,
    ModuleMutations,
    ModuleActions,
    ModuleModules extends ModuleTree<ModuleModules>
  >(path: string, module: Module<ModuleState, ModuleGetters, ModuleMutations, ModuleActions, ModuleModules>, options?: ModuleOptions) => void;

  registerModulePath: <
    ModuleState,
    ModuleGetters,
    ModuleMutations,
    ModuleActions,
    ModuleModules extends ModuleTree<ModuleModules>
  >(path: string[], module: Module<ModuleState, ModuleGetters, ModuleMutations, ModuleActions, ModuleModules>, options?: ModuleOptions) => void;

  // this could be type safe as well, since we're dealing with existing paths
  // but i didn't bother since the whole module concept isn't working :(
  unregisterModule: (path: string) => void;
  unregisterModulePath: (path: string[]) => void;

}

const simpleStoreObject = new Store({
  state: {
    count: 0,
    countString: '1'
  },
  mutations: {
      decrement: (state) => state.count,
  },
  getters: {
    isCountAt10: (state): boolean => { return state.count === 10 }
  },
  actions: {
    decrementAsync: (context) => {
      setTimeout(() => { context.commit('decrement') }, 0);
    }
  }
});

simpleStoreObject.state.count;
simpleStoreObject.state.countString;
simpleStoreObject.state.error;
simpleStoreObject.commit('increment');
simpleStoreObject.commit('decrement');
simpleStoreObject.getters.isCountAt10;
simpleStoreObject.getters.iDontExist;
simpleStoreObject.replaceState({ count: 1, countString: '123123' });
simpleStoreObject.replaceState({ count: '1', countString: 123123 });
simpleStoreObject.dispatch('incrementAsync');
simpleStoreObject.dispatch('decrementAsync');
simpleStoreObject.modules.a.state.propA;

// nothing works for modules
// if I had a Pick in TS that instead of returning me an object that
// contains the property, I could just get the value of that property
// maybe it'd work

const fractalStoreObject = new Store({
  modules: {
    a: {
      state: {
        propA: '123'
      },
      mutations: {
        updatePropA: (state, payload) => state.propA = payload
      },
      getters: {
        isPropA123: (state): boolean => state.propA === '123'
      }
    },
    b: {
      state: {
        propB: 123
      },
      mutations: {
        updatePropB: (state, payload) => state.propB = payload
      },
      getters: {
        isPropB123: (state): boolean => state.propB === 123
      }
    }
  }
});

About the discussion of adding RxJS support, let me add my two cents:

One of the main reasons I switched to Vue after having worked a lot in Angular is that it doesn't force Observables everywhere when they are not absolutely required (it doesn't even mention them).

I have worked what I think is quite a lot with RxJS, I have even made contributions to NgRX (the Redux-like Angular equivalent to Vuex) and PRs to Angular itself. And after working heavily with RxJS, I found out that it ends up being more useful only when absolutely necessary. For many cases, it ended up being a very complex overkill. Very difficult to debug, as you can't use them directly (e.g. in the browser console) but you have to create functions that you pass to RxJS' operators and then wait for them to do their job. At that point debugging ends up having to be with just console.logs, instead of being able to explore stuff live. And the operators, as powerful as they are, are not necessarily very intuitive. The differences between many of the operators are very subtle.

For me, the only clear example of the advantage of using Observables over alternatives was HTTP API throttling or "debouncing", and that is achieved in the official Vue.js guide in a simpler way with just lodash.

But my main point is, as powerful as RxJS is, it's quite complex, and now I suggest not using it unless it's very clear what advantage it's actually providing or which problem it is actually solving better.

Adding a layer of RxJS that exposes the store as Observables might be a very good idea for an external package, so that developers that have a strong requirement for RxJS can use it.

But I would recommend against using it as the main Vuex interface for all the developers.

So I had been working a library of late, (which is picking a bit of traction, like few hundred downloads a day). The aim wasn't exactly to replace/rewrite Vuex with types but add a Typed wrapper, pretty similar to what vue-class-component and vue-property-decorators does with Vue components.

https://github.com/championswimmer/vuex-module-decorators

It heavily depends on you describing your store in form of modules, and each module is then created like a ES6 class (syntactic sugar of decorators - which basically just create the prototype of the class as the old-school vuex module object).
The advantage is, we can describe -

  • state fields simply as class fields
  • getters as simply ES6 get proxies
  • mutations as functions (needs the @Mutation decorator)
  • actions as async functions (needs the @Action decorator)

Since the module is an ES6 class, the fields, getters, mutations and actions are all described as fields and methods of the class - giving them 100% type safety, autocomplete, refactoring support.

Modules are written somewhat like this

import {Module, VuexModule, Mutation, Action} from 'vuex-module-decorators'

@Module
export default class Counter2 extends VuexModule {
  count = 0

  @Mutation increment(delta: number) {this.count+=delta}
  @Mutation decrement(delta: number) {this.count-=delta}

  // action 'incr' commits mutation 'increment' when done with return value as payload
  @Action({commit: 'increment'}) incr() {return 5}
  // action 'decr' commits mutation 'decrement' when done with return value as payload
  @Action decr() {this.decrement(5)}
}

Accessing this module from other components works this way

import { getModule } from 'vuex-module-decorators'
import Counter2 from '@/store/modules/counter2'

const c2 = getModule(Counter2)

await c2.incr() // call directly actions

c2.decrement(1) // call directly mutations

console.log(c2.count) // access state fields

I also started working on a solution for vuex typings and came out with something pretty similar to @championswimmer, though still not as mature: vuex-simple
In the new version I brought out not to long ago there are however some interesting and unique improvements:

  • We just write normal typescript classes. This means that we can use everything that would normally be possible, which also includes inheritance and generics. The decorators don't apply any logic and just store some metadata used later on by the library.
class FooModule {
  @State()
  public counter: number;

  constructor(nb: number = 0) {
    this.counter = nb;
  }

  @Mutation()
  public increment() {
    this.counter++;
  }
}
  • We can have multiple module instances if necessary. The above would give us two submodules 'foo1' and 'foo2' with different initial values.
class BarModule {
  @Module()
  public foo1 = new FooModule(5);

  @Module()
  public foo2 = new FooModule(10);

  @Getter()
  public get total() {
    return this.foo1.counter + this.foo2.counter;
  }
}
  • We can also easily create multiple stores from our defined module classes:
const bar1 = new BarModule();
// transforms and binds 'bar1' to the created store
const store1 = createVuexStore(bar1, {
  strict: true,
  modules: {}, // you can still add normal vuex modules
  plugins: []
})
// we can then call our mutations, getters and such as we normally would with our class instance
bar1.foo1.increment();
console.log('bar1.foo1.counter', bar1.foo1.counter); // 6

// another store
const bar2 = new BarModule();
const store2 = createVuexStore(bar2)
console.log('bar2.foo1.counter', bar2.foo1.counter); // 5

Any official news in 2019?

@xqdoo00o IIRC, vuex is kinda limited with TypeScript because of vue core API which doesn't provide enough types for TypeScript users - types are actually built with flow.

Vue 3.0 will switch from flow to TypeScript for the internal API which will make things much easier for vuex (and other vue-based modules with TypeScript).

Here are slides from Evan You at Vue Toronto Conference announcing what is coming for Vue 3.0.

You can also take a look at the roadmap on vuejs/vue GitHub projects, which have been preferred instead of the traditional roadmap repo now archived.

The 3.0 alpha release will maybe be released in Q1 2019, as it is written in the roadmap.

So, I finally managed to get full type inference for standard vuex module's state/getters/dispatch/commit without any additional boilerplate. You can look at it here:
https://github.com/sascha245/vuex-context

In the end, there isn't much code but getting it to work correctly was a lot more complicated than I thought...

Nice succinct solution @sascha245! Going to be trying this out soon. Is it still worth mentioning in 2019 that it requires ES6?

Also see https://github.com/victorgarciaesgi/Vuex-typed-modules for yet another approach. Looks like we're all hanging out for Vue 3.

@mrcrowl Thanks! And yes my solution makes use of ES6 Proxies, though that shouldn't be too big of a problem nowadays as even Vue is going to use them in the next version ^^

I didn't know about this library yet, but it's looks like we got similar ideas concerning the typings. Though thanks to it I found one thing I could improve on my typings for the getters.

Any official news on how this will probably be implemented on Vuex 4?

Seems that vuex-module-decorators is the most well-maintained workaround for now, but I'd like to at least know if the official solution will be somewhat similar to that one / vuex-simple (class-based store definition), or vuex-context / vuex-type-helper (object-based store with generated typed helpers).

That way me and my team can more easily migrate to Vue 3 / Vuex 4 in the future.

Thank you in advance

I also started working on yet another library for writing ES6/typescript classes as vuex modules without having to use decorators but with full type completion support. Also it is possible to write mutators (which I hope will be removed in vuex 4) as ES6 setter functions: vuex-typesafe-class.

I hope vuex 4 will be close to that (except the need to use mutators).

garyo commented

I'm also starting on a fairly large Vue/Vuex typescript app. I'm trying to figure out best practices for dir layout and module structure. I think this makes sense:

store/
  store.ts
  store-types.ts
  moduleA.ts
  moduleA-types.ts

but if I'm going to have submodules, maybe deeper nesting is best:

store/
  store.ts
  store-types.ts
  moduleA/
    moduleA.ts
    moduleA-types.ts
    subA/
      subA.ts
      subA-types.ts
  moduleB/
    moduleB.ts
    moduleB-types.ts

(I don't like lots of files named 'index.ts'; they all look the same. I prefer files to be named for what they are.)
So does this latter structure look good? It seems like you do need a foo-types.ts for each module so the Vue components can import the module's shape, right? (I suppose you could put all types in store/store-types.ts though -- they don't create any Javascript output, right?)
It does seem overly deeply nested to me; I don't like adding extra dir layers unless needed.
Is there a "best practices" document on how to do this?

I tackled it here - with no additional code, just better typing:

https://github.com/ClickerMonkey/vuex-typescript-interface

I started where @danielfigueiredo did, but figured out I could automatically detect getters, mutations, and actions all from one interface.

So now state, getters, commit, and dispatch all require valid strings and types or TS will throw an error. If you also have state/getters/actions/mutations defined on your interface and NOT in your store it will throw an error. The getter/mutation/action definitions passed in as options must have the proper types or it will also throw an error here as well!

Hi All,

Super interesting comments.
Thank you all !

If I can ask ... (we're now October 2019 and we're all using Vue 3) ...
Any official news on this subject ?
In other words ... What's the proper way to implement Vuex Typescript in a Vue 3 Project ?

Regards,
Johnny

garyo commented

I don't know about Vue 3 (I'm waiting for the official release) but I do have a reasonably clean, complete Typescript-typesafe vuex-module-decorators example at https://github.com/garyo/vuex-module-decorators-example.git if you're interested.

paleo commented

I found a solution for the code that uses the store and calls dispatch, commit, getters and the state. For example:

store.dispatch("myModule/myAction", myPayload);

… is replaced by a wrapper:

store.dispatch.myModule.myAction(myPayload);

… which is fully typed.

https://github.com/paleo/direct-vuex

this worked for me:

// in vuexCommitFuncVars.ts
export default {
  decreaseCount: "decreaseCount",
  ...
};

// in actions.ts
import funcVars from "./vuexCommitFuncVars"; 
commit(funcVars.decreaseCount);
// before i had to write 'commit("decreaseCount")'

// in mutations.ts
[funcVars.decreaseCount](state) {
  state.count= state.count- 1;
},

Any official news in 2020?

IIRC, vue next (aka vue 3) is on an alpha release, we still need to wait for the official vue ecosystem to update, and in the future this will (most probably) entirely handled natively by vuex.

Please correct me if I’m wrong.

@AlenQi I'm using Vue 2 and Vuex with TS without any problems.
Here you can see how I use it: https://github.com/MikeMitterer/vuetify-ts-starter/blob/master/src/views/About.vue (CounterStore)
And here
https://github.com/MikeMitterer/vuetify-ts-starter/tree/master/src/store
how everything is glued together. Hope this helps.

viT-1 commented

@AlenQi
Vue 2 typescript examples with vuex & without webpack/rollup (tsc transpiling & bundling):

  • simple with SystemJs module resolving only
  • tough with esm & SystemJs support, eslint & jest (gulp-replace for esm modules resolving)

So I made a small library wich allows you to get fully typed store in actions and components.
It also adds helpers that allow you to import rather than us mapXXX or decorators

https://github.com/owlsdepartment/vuex-typed

For me adding the store state type in module declaration is enough to have auto-completion

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

A more complete exemple :

import Vue from "vue"
import Vuex, { StoreOptions} from "vuex"
import App from './App.vue'

Vue.use(Vuex)

interface RootState {
  user: App.Models.User | null;
  notifications: App.Models.Notification[];
}

const store: StoreOptions<RootState> = {
  state: {
    user: null,
    notifications: []
  },
  mutations: {
    login(state, user: User) {
      state.user = user
    },
    logout(state) {
      state.user = null
    }
  },
  actions: {
  },
  modules: {
  }
}

store = new Vuex.Store<RootState>(store);

declare module "vue/types/vue" {
  interface Vue {
    // https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    $store: Store<RootState| any>;
  }
}

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

The can't overwrite Store<any> issue (originally tracked in #994) will be resolved in Vuex 4 - beta version release notes here

Vuex has created a lot of pain for Typescript users. Now that we have the Composition API... do we even really need Vuex? In 2020, if I'm training a team to use Vue 3, why would I teach them Vuex? Creating stateful singletons that you can compose is a really nice pattern. Code is so much cleaner without Vuex.

The arguments in favor of Vuex (in my mind) are:

  • Vue devTools integration (time travel, etc.)
  • Slightly easier to avoid name collisions with namespace modules?

What am I missing?

Vuex has created a lot of pain for Typescript users. Now that we have the Composition API... do we even really need Vuex? In 2020, if I'm training a team to use Vue 3, why would I teach them Vuex? Creating stateful singletons that you can compose is a really nice pattern. Code is so much cleaner without Vuex.

The arguments in favor of Vuex (in my mind) are:

  • Vue devTools integration (time travel, etc.)
  • Slightly easier to avoid name collisions with namespace modules?

What am I missing?

Agree, I've trained my teammate to use Vue's injection to provide shared data between components.

garyo commented

This is true, but Vue dev tools integration is not a small thing. It can be extremely useful for debugging.

I'm kinda facing the same issue here, this is how my actions.ts looks like:

export default {
  updateIp(context: any, ip: string) {
    context.commit('setIp', ip);
  }
}

But I get this warning:

  2:21  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

Any idea how to fix this?

A shout out to @paleo, direct-vuex is fantastic. Strongly typed coding within the store and (perhaps more importantly) when components access the store state and actions.

I tried Vuex 4 today, but it didn't really do much for me (I'm still on Vue2 + Composition add-on). Is there an outline of how Vuex 4 will work for Typescript users? All I could find was an old roadmap with generic statements about "supporting Typescript"

@rfox12 As far as I know, you'll need to wait for Vuex 5. Vuex 4 was focused on preparing for Vue 3 and allowing passing your own interface to Store<>

Before Vuex 4, you'd have to do:

new Vue({
  store: new Store(store) as Store<any>
})

thus this.$store.state wouldn't be typed. Working around that (passing your own store interface instead of any) required some trickery

I'll add two additional compelling reasons to keep Vuex in Composition API world:

  • Support for Hot module reloading
  • The string descriptors can be useful if you need to trigger actions from a server (e.g. sockets)

So in short... I'm keeping Vuex for large projects, but I have an question. First some background. To get typescript support today I find it best to import store from './store'; and then use something like store.dispatch.core.signOut() (with direct-vuex). Inside my single-page components. This is instead of using this.$store (which I cannot get Typescript to understand yet--still on Vuex 3). My question is: is there anything magical about Vue's instance of the store that I should be aware of? Any wrapping or logic that would make this.$store different from the imported version?

@rfox12 You should always use this.$store if you're doing SSR. Otherwise, it doesn't matter.

The difference with direct import is that when you use this.$store, you're using the store instance injected into the Vue instance. When doing SSR, at server side, if you use direct import, the store instance will be shared between the different requests because the store instance becomes a global singleton. You might get state pollution.

If you're not doing SSR, it's fine to directly import the store since it will be used by only one client (in browser).

Teebo commented

@MikeMitterer thank you so much, I will go through the repo to understand the setup, but from a glance, it looks good, thank you!

Teebo commented

@MikeMitterer, in the file https://github.com/MikeMitterer/vue-ts-starter/blob/master/src/store/utils/index.ts
The isNotRegistered func:
Is the check mainly for checking if the child store module is registered or it also checks the existence of the RootStore and if it has a state?
I am just wondering if the built-in hasModule could be used.

I see that in the actions return promises in the counter store, is the a requirement from vuex-module-decorators or it is just a usecase example?

Been subscribed to this issue for like 2 years, the absolute state of webdev. Glad I dropped this stuff, yikes.

It is v4.0.0-rc.2 now.
But it seems to haven't no TypeScript supporting for Mutations and Actions.
Why do the Mutations or Actions have to be used by giving identifier string to trigger?
So, will there be any new feature or API to implement type suporting for mutations and actions?

@ChanningHan I've been working on full type support for vuex in my personal time, and with TS 4.1 it seems definitely possible. The only thing holding it back is a problem with TS where it obnoxiously warns about "potentially infinite blah blah". I will be updating this issue: #1831

@ClickerMonkey Bravo~It will be exciting!
ActuaIly, I used to be a heavy user of Vuex before using TS. So, I really hope to see the full type support for vuex, and thank you for your hard working on itβ™₯

Spent several hours trying to modularize vuex store with TypeScript, I feel TS is just a way to write your code two times more. I do understand the importance of type safety, but I gave up trying to describe all types...

Spent several hours trying to modularize vuex store with TypeScript, I feel TS is just a way to write your code two times more. I do understand the importance of type safety, but I gave up trying to describe all types...

TypeScript works best when you just describe a few core domain types & the rest of your code has types inferred from how you operated on those core types & library types. It's especially important for libraries/frameworks to export APIs with types; a few anys sneaking in from libraries creates a lots of manual typing overhead.

How should you write getters? I'm using this example: https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage

Doing store.getters['foo/bar'] doesn't work because it returns 'any', how do you type hint these?

You can't. Its the same as in Vue 2. The newest version of Vuex is mainly for Vue 3 support. Wait for Vuex 5 or use vuex-module-decorators (or similar pkgs).

There's also an issue in this repo that proposes using TS 4.1 template literal types for typing

@francoism90

@3nuc I'm new to TypeScript so don't know what any of that means lol. As a workaround I'll be sticking to mapGetters as TS doesn't complain about this. State injection seems to working, but using helpers defeats the point of TS I think.

Will take a look at vuex-module-decorators, although I'll probably wait for Vuex 5. Thanks!

https://github.com/posva/pinia - looks pretty good. Works with TypeScript out of the box.

@usernamehw Yeah, I also using Pinia as TS replacement. :)

People who prefer classical OOP-based concepts and not too functional looking code, can check out my library

https://github.com/championswimmer/vuex-module-decorators