Giter VIP home page Giter VIP logo

vuex-typescript's Introduction

Vuex-Typescript Build Status Coverage Status npm version

https://github.com/istrib/vuex-typescript/

A simple way to get static typing, static code analysis and intellisense with Vuex library.

We get full end-to-end compile-time safety and code navigability:

  • No string literals or constants for action/mutation/getter names
  • No action/mutation/getter misuse by providing wrong payload type
  • Intellisense giving unambiguous hints on what type of payload or getter arguments is expected
  • Refactoring made easy
  • Supports vuex with and without modules (though use of modules and namespaces seems to produce better structured code).

The idea

This library does not change the way how vuex handlers are defined (in particular it does not make you use classes (though it does not stop you, either).

The library changes the way how you call the store, once you have its instance: you don’t use store’s compile-time-unsafe methods but you use strongly typed accessors instead. This approach is remotely similar to the pattern of Redux Action Creators, though much less boilerplate is needed thanks to higher-order functions provided by this library which do the dull work.

Example

Full working example available here. Excerpt:

import { ActionContext, Store } from "vuex";
import { getStoreAccessors } from "vuex-typescript";
import { State as RootState } from "../state";
import { BasketState, Product, ProductInBasket } from "./basketState";

// This part is a vanilla Vuex module, nothing fancy:

type BasketContext = ActionContext<BasketState, RootState>;

export const basket = {
    namespaced: true,

    state: {
        items: [],
        totalAmount: 0,
    },

    getters: {
        getProductNames(state: BasketState) {
            return state.items.map((item) => item.product.name);
        },
		...
    },

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

    actions: {
        async updateTotalAmount(context: BasketContext, discount: number): Promise<void> {
            const totalBeforeDiscount = readTotalAmountWithoutDiscount(context);
            const totalAfterDiscount = await callServer(totalBeforeDiscount, discount);
            commitSetTotalAmount(context, totalAfterDiscount);
        },
		...
    },
};

// This is where the vuex-typescript specific stuff begins:
//
// We want to expose static functions which will call get, dispatch or commit method
// on a store instance taking correct type of payload (or getter signature).
// Instead of writing these "store accessor" functions by hand, we use set of higher-order
// functions provided by vuex-typescript. These functions will produce statically typed
// functions which we want. Note that no type annotation is required at this point.
// Types of arguments are inferred from signature of vanilla vuex handlers defined above:

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

export const readProductNames = read(basket.getters.getProductNames);
export const dispatchUpdateTotalAmount = dispatch(basket.actions.updateTotalAmount);
export const commitAppendItem = commit(basket.mutations.appendItem);

And then in your Vue component:

import * as basket from "./store/basket"; // Or better import specific accessor to be explicit about what you use
...

getterResult = basket.readProductNames(this.$store); // This returns Product[] 
basket.dispatchUpdateTotalAmount(this.$store, 0.5); // This accepts number (discount) - you'd normally use an object as arguments. Returns promise.
basket.commitAppendItem(this.$store, newItem); // This will give compilation error if you don't pass { product: Product; atTheEnd: boolean } in

Vuex version compatibility

For Vuex 2.x use newest vuex-typescript 2.x

For Vuex 3.x use newest vuex-typescript 3.x

This library has explicit dependency on Vuex. A new version of vuex-typescript is released following each major release of Vuex. This way breaking API changes introduced into Vuex are guaranteed to be followed up and tested in vuex-typescript.

Functions or objects

This lib is deliberately designed with functions rather than classes. This does not stop you from grouping accessors into objects. Note however that this makes little sense as the accessors are loosely or not related to each other. Importing and using functions rather than objects makes it explicit which accessor you actually use rather than stating which accessor in an object you may be using.

If you wish to define your vuex handlers as class members then you must decorate these methods with @Handler decorator exported from this library as shown in this test.

More

https://github.com/mrcrowl/vuex-typex also uses higher-order functions but takes a few steps more: there the store is implicitly built while defining accessors for each handler (in contrast here we create store with standard Vuex options and then wrap handlers into accessors). It is also not required to pass $store as argument to accessors. Definitely worth checking out.

Contributing

npm run build
npm run build-watch

npm test
npm run test-watch
npm run test-debug
npm run coverage

vuex-typescript's People

Contributors

istrib avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vuex-typescript's Issues

The GetterHandler is not update

Hi,
If i set a property with readProductNames values and then commitAppendItem, the property won't update.
If i call again readProductNames it will show the correct data, but i was expecting for a getter to be always updated according to the state.

Help please,

Thanks,
Tatiana.

Optional parameters in Actions not supported

Given an action wit optional parameter:
async testIt(context: TestContext, payload?: number): Promise<void> {}

Is mapped with:
export const testAction = dispatch(test.actions.testIt)

When you call the action inside a VUE Component with the optional parameter:
test.testAction(this.$store, 123)

You will get a compile error:
Expected 1 arguments, but got 2.

Call getter in another module?

From an action in one module I want to call a getter in another module. I'm able to do it with context.rootGetters["namespace/getter"] but it doesn't feel like the typescript way to do it. Trying to call the read static function I can't figure out how to get the right ActionContext to pass into the getter.

Accessing functions from outside modules

Is there a way to access the store functions ive set up in router guard methods, they all work fine inside modules themselves but i cant seem to get them working outside of those.

How to use handler with parameters in class

Im not clear on the intent of the target parameter and how i would use that so i can leverage the key parameter

export declare function Handler(target: any, key: string): void;

 @Handler( ??? , 'dispatchIncrementCounter')
    private _dispatchIncrementCounter(context: MarketContext): void {
        context.commit('incrementCounter');
    }


//store
    public market = {
        namespaced: true,
        ...
        actions: {
            dispatchIncrementCounter: this._dispatchIncrementCounter,
      }
    }

I have also noticed that when using @handler the context of this for a class is no longer valid. How can i get the class reference for using injected services from the constructor?

 constructor( @inject(INJECTION_TYPES.ProductService) private _productService: IProductService) {
        super();
    }
...

@Handler
    private async dispatchFindProductsByCategory(context: MarketContext, payload: { category: string }): Promise<void> {
        let d = await this._productService.getAsync();
}

  market = {
        namespaced: true, // this is required 
        state: this._initialMarketState,
        actions: {
            dispatchFindProductsByCategory: this.dispatchFindProductsByCategory
        }
    }

  findProductsByCategoryAsync = async (payload: { category: string }): Promise<void> => {
        return await dispatch(this.market.actions.dispatchFindProductsByCategory)(this.store, payload);
    };




this._productService is UNDEFINED

Any insight would be great

Why is my getter from store in App.vue class undefined

I am building an electron app using vue, vuex and vuex-typescript. I have been following this example: https://github.com/DevoidCoding/TodoMVC-Vue-TypeScript/tree/master/vuex-typescript
So I have the following code for my store using vuex-typescript:

export const dispaVuex = {
  namespaced: true,

  state: {
    dispatch: new Dispatcher(appState),
  },

  getters: {
    getFTime(state: DispatchState): boolean {
      return state.dispatch.fistTime;
    },
  },
};

const {read} = getStoreAccessors<DispatchState, RootState>("Test");

export const readFtime = read(dispaVuex.getters.getFTime);

After adding the store to my vue instance I try and access the firstTime variable in my App.vue like this:

@Component
export default class App extends Vue {
  get fTime(): boolean {
    return readFtime(this.$store);
  }
}

When looking at the debugger, everything in the store is initialized perfectly but my App instance has fTime as undefined.

Why would this be the case? Is there something I am not getting about the order of how things are made?

PS. firstTime is a member of the class Dispatcher

How do I tell typescript that this.$store is any

Hi,

sorry for the beginner question, I am trying to do a migration for a project. As first step I would like to have this.$store treated as any.

Is there a way to specify this for all components?

thank you for your time and happy holidays

why is my initial state in vuex store undefined?

Using vuex-typescript and getting inital state undefined all the time. Once i reset state it is working but only when i reset and window refresh. Here is my setup for some simple module:

import Vue from "vue";
import Vuex from "vuex";

import { module } from "./some_module";

let store = new Vuex.Store({
  modules: {
    module,

  },
  plugins: [persist({
    namespace: "mainnamespace",
    initialState: {
    },
    expires: 7 * 24 * 60 * 60 * 1e3 // 1 week
  })
  ]
});

some_module.ts

export interface State {
  something: any;
}

const state: State = {
  something: [],
};

export const module = {
namespaced: true,
  getters: {

    getSomethingArray: (state: State) => {

      return state.something;
    },

  },
  mutations: { 

    resetState: (s: State) => {
      const initial = state;
      Object.keys(initial).forEach(key => { s[key] = initial[key]; });
    },
  }
  actions: {///}
}

const { commit, read, dispatch } =
  getStoreAccessors<HistoryState, any>("somemodule");
const mutations = module.mutations;
const getters = module.getters;
const actions = module.actions;
export const getSomeStuffFromHere = read(getters.getSomethingArray);

when i run app and console.log(getSomethingArray(this.$store)) i got undefined and when i console.log(this.$store) i can see somemodule nemespace but its state is not something: [] but some __ob__: Observer

This happens completely random. In some modules it does not happen at all and there is no difference in them except state object.

Getters always returning undefined where state is valid

Stumbled across this lib through this article https://medium.com/js-dojo/using-fully-typed-vuex-mutations-with-vuex-typescript-7597f56eceec. All is working well but getters are always returning undefined when using dot notation to get into the state.

If i parse in my UserStoreState object into the getter there is data but it is the entire user state, as soon as I try to only access the user object within the state I get undefined even though there is data within it. Unsure if this is because namespacing is false

Vuex Store

import Vue from 'vue'
import Vuex, { Store } from 'vuex'
import UserStore from '@/vuex/user'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

const store: Store<any> = new Vuex.Store({
  modules: {
    user: UserStore,
  },
  strict: debug,
})

Vue.prototype.$store = store
export default store

User Store Module

import { UserStoreState } from '@/vuex/module/UserStoreState'
import { User } from '@/model/user/User'

export const UserStoreModule = {
  namespaced: false,
  state: new UserStoreState(),
  mutations: {
    setUser (state: any, user: User): void {
      state.user = user
    },
  },
  getters: {
    user: (state: UserStoreState): User => {
      return state.user
    },
  },
}

UserStore

import BaseStoreService from '@/vuex/util/BaseStoreService'
import { UserStoreState } from '@/vuex/module/UserStoreState'
import { UserStoreModule } from '@/vuex/module/UserStoreModule'
import { User } from '@/model/user/User'

class UserStore extends BaseStoreService<UserStoreState> {
  public mutations = UserStoreModule.mutations
  public getters = UserStoreModule.getters

  setUser (user: User) {
    this.commit<User>(this.mutations.setUser, user)
  }

  getUser (): User {
    return this.read<User>(this.getters.user)
  }
}

export default new UserStore()

BaseStoreService

import { getStoreAccessors, GetterHandler, MutationHandlerWithPayload } from 'vuex-typescript'
import store from '@/plugins/vuex'
import { RootState } from '@/vuex/state'

export default abstract class BaseStoreService<T> {
  protected api = getStoreAccessors<T, RootState>('');

  protected commit<TPayload> (handler: (MutationHandlerWithPayload<T, TPayload>), payload: TPayload) {
    this.api.commit(handler)(store, payload)
  }

  protected read<TResult> (handler: GetterHandler<T, RootState, TResult>): TResult {
    return this.api.read<TResult>(handler)(store)
  }
}

tempsnip

Docs about using Handler

// The pattern shown here IS NOT RECOMMENDED.

In Chromium 30 (which is the version of the Android 4.2.2 WebView) Function.name is not spec complaint, and vuex-typescript relies on it being so. More specifically (function () { var x = function () { }; return x.name; })() should return "x", but in Chromium 30 returns "" since it is an anonymous function. For this situation I found using classes with the Hander decorator is absolutely necessary.

Since Function.name is an es2015 feature https://www.ecma-international.org/ecma-262/6.0/#sec-setfunctionname, and some vuex-typescript users will be targeting mobiles, it would be helpful documenting this use case; perhaps in the readme.

Using strongly typed vuex in other modules

Is there any way I can use this vuex-typescript in modules like vue-router, where I do not have access to this.$store? My use case is such that I am checking if user is authenticated by using getter from vuex in beforeEach hook in vue-router

ERROR: Vuex handler functions must not be anonymous. handler.name in IE and Safari is undefined

I followed you example (withModules) and app is not working at all in IE and Safari. I am getting error:
Vuex handler functions must not be anonymous. Vuex needs a key by which it identifies a handler. If you define handler as class member you must decorate it with @Handler.

Now my code is this (some of it):

export const search_ = {
namespaced: true,
  getters: {
    searchedAddress: (state: State) => {
      return state.searchedAddress;
    }
  },
  mutations: {
    searchingFor (state: State, address: Address) {
      state.searchedAddress = address;
    },
    resetState: (s: State) => {
      const initial = state;
      Object.keys(initial).forEach(key => { s[key] = initial[key]; });
    },
  }
}

const { commit, read, dispatch } =
  getStoreAccessors<State, any>("search_");
const getters = search_.getters;
export const getSearchedAddress = read(getters.searchedAddress);

When i console log your stuff in this function:

function qualifyKey(handler, namespace) {
  console.log(namespace)
  console.log(handler)
  console.log(handler.name)
  console.log(handler._vuexKey)
    var key = handler.name || handler._vuexKey;
    if (!key) {
        throw new Error("Vuex handler functions must not be anonymous. "
            + "Vuex needs a key by which it identifies a handler. "
            + "If you define handler as class member you must decorate it with @Handler.");
    }
    return namespace
        ? namespace + "/" + key
        : key;
}

I am getting in IE and Safari for Windows:

  1. search_ // this is namespace
  2. function as string // this is ahndler
  3. undefined // this is handler name (but should be mutation name)
  4. undefined // this is handler._vuexKey

I would really appreciate if you can help with this because this is a project that i have been working on for three months and going back to old style vuex is just not and option for me at this momment.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.