Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript typed vuex #532

Closed
ubershmekel opened this issue Dec 19, 2016 · 10 comments
Closed

Typescript typed vuex #532

ubershmekel opened this issue Dec 19, 2016 · 10 comments
Labels
duplicate This issue or pull request already exists enhancement New feature or request types Related to typings only

Comments

@ubershmekel
Copy link

ubershmekel commented Dec 19, 2016

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?

@ktsn
Copy link
Member

ktsn commented Dec 21, 2016

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

@ktsn ktsn added the enhancement New feature or request label Dec 21, 2016
@Glidias
Copy link

Glidias commented Jan 20, 2017

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..).

@dengwanc
Copy link

dengwanc commented Aug 9, 2017

@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.

@joseluisq
Copy link

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

@ktsn ktsn added the types Related to typings only label Jan 11, 2018
@justerest
Copy link

justerest commented Apr 17, 2018

@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?

@justerest
Copy link

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

@joevandyk
Copy link

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?

@ReinisV
Copy link

ReinisV commented Mar 13, 2019

@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.

@ClickerMonkey
Copy link

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.

@kiaking
Copy link
Member

kiaking commented Feb 3, 2020

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

@kiaking kiaking closed this as completed Feb 3, 2020
@kiaking kiaking added the duplicate This issue or pull request already exists label Feb 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate This issue or pull request already exists enhancement New feature or request types Related to typings only
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants