Typescript class decorators for vuex modules
This is yet another package to introduce a simple type-safe class style syntax for your vuex modules, inspired by vue-class-component.
npm install vuex-class-modules
And make sure to have the
--experimentalDecoratorsflag enabled.
Both a
commonjsand a
esmmodule build are published. If you have a webpack-based setup, it will use the
esmmodules by default.
Vuex modules can be written using decorators as a class:
// user-module.ts import { VuexModule, Module, Mutation, Action } from "vuex-class-modules";@Module class UserModule extends VuexModule { // state firstName = "Foo"; lastName = "Bar";
// getters get fullName() { return this.firstName + " " + this.lastName; }
// mutations @Mutation setFirstName(firstName: string) { this.firstName = firstName; } @Mutation setLastName(lastName: string) { this.lastName = lastName; }
// actions @Action async loadUser() { const user = await fetchUser(); this.setFirstName(user.firstName); this.setLastName(user.lastName); } }
// register module (could be in any file) import store from "path/to/store"; export const userModule = new UserModule({ store, name: "user" });
The module will automatically be registered to the store as a namespaced dynamic module when it is instantiated. (The modules are namespaced to avoid name conflicts between modules for getters/mutations/actions.)
The module can then be used in vue components as follows:
// MyComponent.vue import Vue from "vue"; import { userModule } from "path/to/user-module.ts";export class MyComponent extends Vue { get firstName() { return userModule.firstName; // -> store.state.user.firstName } get fullName() { return userModule.fullName; // -> store.getters["user/fullName] }
created() { userModule.setFirstName("Foo"); // -> store.commit("user/setFirstName", "Foo") userModule.loadUser(); // -> store.dispatch("user/loadUser") } }
rootStateand
rootGetters?
There are two ways to access other modules within a module, or dispatch actions to other modules.
// my-module.ts// import the module instance import { otherModule } from "./other-module";
@Module class MyModule extends VuexModule { get myGetter() { return otherModule.foo; }
@Action async myAction() { await otherModule.someAction(); // ... } }
// my-module.ts// import the class, not the instance import { OtherModule } from "./other-module";
@Module export class MyModule extends VuexModule { private otherModule: OtherModule;
constructor(otherModule: OtherModule, options: RegisterOptions) { super(options); this.otherModule = otherModule; }
get myGetter() { return this.otherModule.foo; }
@Action async myAction() { await this.otherModule.someAction(); // ... } }
// register-modules.ts import store from "path/to/store"; import { OtherModule } from "path/to/other-module"; import { MyModule } from "path/to/my-module";
export const otherModule = new OtherModule({ store, name: "otherModule" }); export const myModule = new MyModule(otherModule, { store, name: "myModule" });
The local modules will not be part of the state and cannot be accessed from the outside, so they should always be declared private.
myModule.otherModule; // -> undefined
store.watchfunction
Vuex can also be used ouside of vue modules. To listen for changes to the state, vuex provides a watch method.
This api is also provided by vuex-class-modules under the method name
$watchto prevent name collisions. For example you can do:
import store from "./store"; import { MyModule } from "./my-module";const myModule = new MyModule({ store, name: "MyModule" }); myModule.$watch( (theModule) => theModule.fullName, (newName: string, oldName: string) => { // ... }, { deep: false, immediate: false, } );
and to unwatch:
const unwatch = myModule.$watch(...); unwatch();
name[required]: Name of the module
store[required]: The vuex store - which can just be instantiated as empty:
// store.ts import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({});
The module decorator can also accept options:
generateMutationSetters[optional, default=false]: Whether automatic mutation setters for the state properties should be generated, see Generate Mutation Setters.
The vuex shopping cart example rewritten using
vue-class-componentand
vuex-class-modulescan be found in the example directory. Build the example using:
npm run example
this
As for vue-class-component
thisinside the module is just a proxy object to the store. It can therefore only access what the corresponding vuex module function would be able to access:
@Module class MyModule extends VuexModule { foo = "bar";get someGetter() { return 123; } get myGetter() { this.foo; // -> "bar" this.someGetter; // -> 123 this.someMutation(); // undefined, getters cannot call mutations this.someAction(); // -> undefined, getters cannot call actions }
@Mutation someMutation() { /* ... */ } @Mutation myMutation() { this.foo; // -> "bar" this.someGetter; // -> undefined, mutations dont have access to getters this.someMutation(); // -> undefined, mutations cannot call other mutations this.someAction(); // -> undefined, mutations cannot call actions }
@Action async someAction() { /* ... */ } @Action async myAction() { this.foo; // -> "bar" this.someGetter; // -> 123 this.myMutation(); // Ok await this.someAction(); // Ok } }
The module can have non-mutation/action functions which can be used inside the module. As for local modules, these functions will not be exposed outside the module and should therefore be private.
thiswill be passed on to the local function from the getter/mutation/action.
@Module class MyModule extends VuexModule { get myGetter() { return myGetterHelper(); } private myGetterHelper() { // same 'this' context as myGetter }@Mutation myMutation() { this.myMutationHelper(); }
// should be private myMutationHelper() { /* ... */} } const myModule = new MyModule({ store, name: "myModule }); myModule.myMutationHelper // -> undefined.
As I often find myself writing a lot of simple setter mutations like
@Module class UserModule extends VuexModule { firstName = "Foo"; lastName = "Bar";@Mutation setFirstName(firstName: string) { this.firstName = firstName; } @Mutation setLastName(lastName: string) { this.lastName = lastName; } }
a module option
generateMutationSettershas been added, which when enabled will generate a setter mutation for each state property. The state can then be modified directly from the actions:
@Module({ generateMutationSetters: true }) class UserModule extends VuexModule { firstName = "Foo"; lastName = "Bar";// Auto generated: // @Mutation set__firstName(val: any) { this.firstName = val } // @Mutation set__lastName(val: any) { this.lastName = val }
@Action async loadUser() { const user = await fetchUser(); this.firstName = user.firstName; // -> this.set__firstName(user.firstName); this.lastName = user.lastName; // -> this.set__lastName(user.lastName); } }
NOTE: Setters are only generated for root-level state properties, so in order to update a property of an object you have to use a mutation or replace the entire object:
@Module({ generateMutationSetters: true }) class UserModule extends VuexModule { user = { id: 123, name: "Foo", };@Mutation setUserName() { this.user.name = "Bar"; // OK! }
@Action async loadUser() { this.user.name = "Bar"; // Bad, the state is mutated outside a mutation this.user = { ...this.user, name: "Bar" }; // OK! } }