Vuex: Typescript typed vuex

Created on 20 Dec 2016  路  10Comments  路  Source: vuejs/vuex

Right now it's hard to maintain type safety with Vuex and Typescript because of the way the $store exposes actions and mutations through string-based dispatch and commit. At compile time there's no way to know the strings are valid or that the passed payload has the requisite fields.

There's this valiant effort:

Though that API doesn't really look like the Vuex API to me. Would there be interest in trying to allow typescript type safety in Vuex? Are there existing best practices?

duplicate enhancement types

Most helpful comment

We may improve Vuex type declaration by using keyof and Mapped Types that is introduced on TS 2.1

All 10 comments

We may improve Vuex type declaration by using keyof and Mapped Types that is introduced on TS 2.1

I did some Typescript fiddles approaches, may not be the "perfect" solution unless some form of macroing is available, but seems pretty okay as you should get typehinting/checking right off the board given the necessary boilerplate.

Typescript fiddles: http://tinyurl.com/hzp9ulv

To avoid further boilerplate with re-defining union type per store.commit call, you can encapsulate via a higher-order domain-specific commit/dispatch helper method. eg,

// setup union list of mutation types
export default Mutations = MutationA | MutationB | MutationC;

 // Optional: a custom commit higherorder helper method to encapsulate Mutation type and store context
 // (note: additional wrapper function call overhead though, unless can be inlined??)
 export function CommitHelperFor(store:Store) { 
      return function<P extends Mutations>(payload:P, options?:CommitOptions) { 
     store.commit(payload, options);
        }
}

// another optional helper method, useful especially in Action handlers or varying store contexts
    export function CommitTo<P extends Mutations>(context:Store|ActionContext, payload: P, options?: CommitOptions) { 
        context.commit(payload, options);
}

// .......................................

// In another file... can use commit helper

//optionally   import CommitHelperFor(store) as commit from above.
commit ({ type:"willBeTypedCheck", ...});

    // optionally import CommitTo as commitTo from above.
    commitTo(store, { type:"willBeTypedCheck", ...});

Another altenative is to use HaxeVx. It has a Vuex implementation in Haxe, (but not production-ready yet, got working proof of concept based off Shopping Cart Vuex example) that strictly-types dispatches and commits' payloads fully, and does so without having to manage any strings or constants. For example, for a given Vue component class, all i need to do is call appDispatcher._someAction(store, payload) (this function is macro-generated on the fly..) The generated javascript code will translate that line to this.$store.dispatch("someMacroGeneratedStringBasedOffDispatcherClass", payload) . Or like for a mutator reference, or a mutator reference within an action class function handler, something like appMutator._someMutation(context, payload). translates to context.dispatch("someMacroGeneratedStringBasedOffMutatorClass", payload). For such cases, everything gets inlined and no additional functions are called at runtime.

It leverages on the Haxe compiler and Macro features to ensure a fully static-typed (and customised) development experience for coding both VueJS and Vuex stuff. The inline function _someMutation(context,payload) method helper is auto-generated from the someMutation(state, payload) {} handler function that I wrote in relavant Mutator/Action classes, so whatever i dispatch, i know exactly which function(s) are to receive it. The only thing i need to write are the mutation/action handlers, and everything else is auto-generated for me.

A Design doc for HaxeVx, Vuex, can be found at:
https://github.com/Glidias/haxevx/wiki/Design-doc-::-Vuex-Macros-for-HaxeVX.


Another way for Typescript is to go down runtime Decorator approach, and heavily rely on them to construct something similar to what HaxeVx does. Mock examples:

http://tinyurl.com/go9ap5u

The idea is to have a decorated class instance payloaded function call that actually returns the handler at runtime initialization (even though the function is ret-typed to void or some other action-based return type, <any>function is used to forego this type-check. At runtime, the @mutator or @action decorator will replace it's prototype methods with actual dispatch calls and such after saving the necessary Mutators/Actions hash object's handler methods for that given class (if it hasn't been initialized yet for that class). That was the implementation that I somewhat had in HaxeVx originally (which I eventually replaced with pre-compile macros..).

@yyx990803 @ktsn there is a way keep API compatible and type safety in vuex ?

I try to refactor the declaration file. But Can't maintain the type-payload relationship (mutation -> commit, action -> dispatch), so any is the only way ? I thought can't solve this in the language level unless change the API.

:+1:
By now this is a nice alternative https://github.com/istrib/vuex-typescript

@ktsn , What do you think about mutation types interface like this?

// mutation-types.ts

/**
 * @type {{[mutationName]: payloadType}}
 */
export interface MutationTypes {
  ROOT_INCREMENT: number;
  ROOT_INCREMENT5: void;
  MODULE_INCREMENT: number;
  MODULE_INCREMENT2: void;
}

It can be used if we change type of Store.commit method:

// shims.d.ts

declare module 'vuex' {
  import * as Vuex from '@/../node_modules/vuex';
  export * from '@/../node_modules/vuex';

  import { MutationTypes } from '@/mutation-types';

  export class Store<S> extends Vuex.Store<S> {

    // FIXME: payload is required
    /** @override check payload type */
    commit: <T extends keyof MutationTypes>
    (type: T, payload: MutationTypes[T], options?: Vuex.CommitOptions) => void;

  }

  const DEFAULT: {
    Store: typeof Store;
    install: typeof Vuex.install;
  };

  export default DEFAULT;
}

And now we can just use store.commit without type arguments.
Has this way any potential problems?

I try to solve problems with types at here https://github.com/justerest/vue-ts/tree/master/src

Any movement on this? Is https://github.com/istrib/vuex-typescript what I should probably use if I want better typing in the near future when using vuex?

@joevandyk I actually tried using vuex-typescript for my project, but it turned out to become more of a hassle than an improvement that often times made things more complex.

One issue I remember having very clearly is that if you wanted, say, call an action of a different module from the action of your module, you still had to use the dispatch('action-name', { payload }), which is not typesafe, while calling actions within your own module you could just typesafely execute the method.

I ended up using vuex-simple, which takes vuex all the way into Typescript by having Typescript modules be usable classes for whom the this reference works as you'd expect. Also, if you need inter-module communication, you can just pass a reference to the module in the constructor of the other module.

I tried to tackle it here - with no additional code, just better typing:

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

Not only can you pass a State type to the Store, but it detects getters, mutations, and actions and forces you to use those names and types defined in the interface passed in.

Closing due to duplicated issue #564 (this one is newer and have more comments).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Ge-yuan-jun picture Ge-yuan-jun  路  3Comments

fnlctrl picture fnlctrl  路  4Comments

taoeffect picture taoeffect  路  3Comments

gdelazzari picture gdelazzari  路  3Comments

Sarke picture Sarke  路  3Comments