npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mmm-typed-vuex

v5.0.4

Published

Vuex and TypeScript living in harmony with one another.

Downloads

34

Readme

mmm-typed-vuex (beta 5)

Vuex and TypeScript living in harmony with one another 🎶

Installation

$ yarn add mmm-typed-vuex
$ npm install --save mmm-typed-vuex

Okay, but what is mmm-typed-vuex?

This library is all about:

  • fewer string references in your Vue components (those suck)
  • less guess work as to the structure of your store (thank goodness)
  • typed state values, mutation / action payloads, and getters (it's about time)
  • built-in init action for modules (why doesn't this exist by default?)
  • simplify components even further by getting rid of mapState, mapActions, mapMutations, and mapGetters (optional)
  • dispatch actions, get state, etc from anywhere in your code via a single-line command (easy-peasy like)

At its simplest, mmm-typed-vuex proposes something like this (yay typings!):

computed: {
  count(): number { return AppStoreHelper.CounterStore.count; },
},
methods: {
  increment(): void { AppStoreHelper.CounterStore.commitIncrement(2); },
}

Instead of this (boo!):

computed: {
  ...mapState({
    count(state) { return state.CounterStore.count; }
  }),
},
methods: {
  ...mapMutations('CounterStore', {
    incrementMutation(commit): void { commit('increment', 3); }
  }),
}

Why?

I could not be more sick of brittle string references! You know, those annoying set of characters that you copy and paste throughout your code. Yeah, we all know we shouldn't do it, but Vuex makes it especially tempting. Be honest, have you ever done something like this?

// App.vue
computed: {
  ...mapState({
    title: 'title',
    count(state) { return state.CounterStore.count; }
  }),
  ...mapGetters('CounterStore', ['countX10'])
},
methods: {
  ...mapMutations('CounterStore', {
    incrementMutation(commit): void { commit('increment', 3); }
  }),
  ...mapActions({
    decrementAction(dispatch): void { dispatch('CounterStore/decrement', 3); }
  })
}

If so, you now have:

  • static string references to a potentially complex and ever-changing store structure
  • blind references to state properties
  • very brittle code

Disgusting. Definitely not very 'mmm' ;)

What if one of your property or mutuation names change? What if you move, rename, or delete a module? Yep, you're going to have to find and update all those narly string references each time you make a change. Good luck if you're playing around with a substantial codebase.

Adding insult to injury, you sure as heck aren't getting typed state, mutation payloads, etc. Ouch!

Solution

Let the store, alone, define and strictly enforce its data through typings.

Enough talk, let me instead show you one possible alternative to the aforementioned string hell:

// App.vue
computed: {
  // access state through the helper (though you can still use 'mapState' if you choose and still get typings)
  title(): string { return AppStoreHelper.title; },
  count(): number { return AppStoreHelper.CounterStore.count; },
  // getters
  countX10(): number { return AppStoreHelper.CounterStore.getCountX10(); }
},
methods: {
  // convenience method that type-safes the mutation and payload
  increment(): void { AppStoreHelper.CounterStore.commitIncrement(2); },
  // convenience method that type-safes the action and payload
  decrement(): void { AppStoreHelper.CounterStore.dispatchDecrement(2); }
}

Much better! It may appear a little verbose, but it's all typed and your editor's intelli-sense should be able to do all the heavy lifting. Oh...and no more string references!!

Okay, but what is mmm-typed-vuex really?

Honestly, it's not much...which was my main objective. We're talking about roughly 20 or so lines of meaningful code...but there is a dash of magic in there. It's just enough to avoid dealing with module paths, make a module's helper methods more accessible, and provide an init module action.

And now, for the measly sum of $0, all that magic can be yours ;)

Additional documentation

Vuex definition examples:

A quick note, this library attempts to be largely unopinionated and avoid recreating Vuex logic. This leaves the details of the store implementation and how your Vue component consumes it up to you. There's a few example provided show-casing different implementations. The following is just one example. Chances are, you'll find a better way to leverage this simple library...is you so choose :)

// CounterStore.module.ts
export default class CounterStore extends StoreModule {
  // typings (the decorators approach is experimental and several other options can be found in the examples directory)
  @mmmState public count: number;
  @mmmMutation() public commitDecrement(payload: number) { return; }
  @mmmMutation() public commitIncrement(payload: number) { return; }
  @mmmAction() public dispatchDecrement(payload: number) { return; }
  @mmmGetter() public getCountX10(): number { return NaN; }

  constructor() {
    super();

    this.setOptions(
      // this should be familiar...it's what you've already been doing except for (optionally) typing the state object
      {
        namespaced: true,
        state: {
          count: 0,
        } as CounterStore, // optional typing
        getters: {
          getCountX10: (state: CounterStore): number => {
            return state.count * 10;
          },
        },
        mutations: {
          commitDecrement(state: CounterStore, payload: number) {
            state.count -= payload;
          },
          commitIncrement(state: CounterStore, payload: number) {
            state.count += payload;
          },
        },
        actions: {
          initModule: (context: ActionContext<AppStore, AppStore>) => {
            // finally, an easy way to asynchronously initialize state on module load, add 'this._store.watch', etc
          },
          dispatchDecrement: (context: ActionContext<CounterStore, AppStore>, payload: number) => {
            this.commitDecrement(payload);
            // dispatch to another module, in this case the root AppStore (wow is this easy!!!)
            AppStoreHelper.dispatchChangeTitle('My New Title');
          },
        },
      },
    );
  }
}

Additional Notes

  • Those class properties (e.g. public title: string;) found within AppStoreHelper.ts are not -- in any way -- used to set state values (they're there only for typings)
  • Methods for mutations, actions, and getters simply type-safe payloads and simplify module paths

Available Examples

  1. A baseline, no typings example that showcases the brittleness of the typical string-heavy approach: baseline-no-typings
  2. A two-level deep 'module' example with concise, flattened module typing definitions: modules-flat-definitions
  3. An object-defined approach to typings that more clearly separates state, actions, mutations, getters and modules: modules-object-definitions
  4. An ES6 JavaScript example (no TypeScript): js-only

Potential negatives

Dynamic registration of modules can't leverage the provide init action...yet. It's coming!

There's more boilerplate. It sucks, but that's just the reality of it right now.

Rebuttal

On the flip-side, is the fact that once you write a typed vuex module, the rest of the app can effortlessly use it. No more banging your head against the wall trying to figure out the store structure or dealing with the fallout from refactoring all the string references scrattered throught your code. Is it worth the extra setup? That's for you to decide. For me, the answer is a resounding, "YES!". Once I'm done writing a store and am back to building out components and services, the last thing I want to do is to needlessly wrestle with the store. Plus, having the ability to leverage the init action is amazing!

Other ideas

This is surely not the best way this can be done, but it's at least a step in the right direction, I feel.

If you have ideas on how to improve upon this effort or want to contribute in any way, I'd definitely enjoy hearing from you! Two people's ideas are better than one.

Version 5 Notes

  • The init action 'initMmm' has been renamed 'initModule'
  • The store is now available from any modules via 'this._store'

Version 4 Notes

  • Store modules are now set to the 'options' property and the root store and its modules must pass this property in during registration
  • No more 'AppStore.init' in the store.ts
  • The AppStoreHelper is new and makes calls more concise and removes an additional property lookup each call
  • No more recursive lookups for module mutations and actions
  • Init action is now available out of the box

Version 3 Notes

  • Accessing state through 'RootScope' has been simplified and actions, mutations, and getters follow a similar format for consistency
  • Defining module namespaces and parent modules is now done through the super constructor

Version 2 Notes

  • The module helpers are no longer being stored in the store (hooray!)
  • You no longer have to use mapState, mapActions, mapMutations, and mapGetters (of course, you can if you still want to)
  • The method 'getModulePath' now caches the paths it caculates to avoid redundant processing on each request

Conclusion

That's it folks! Enjoy the Vue :)