This in Vuex class modules

July 23rd 2021 Vuex

Vuex module decorators allow Vuex module code to be written in the form of a class, rather than in a structured object that follows a specific convention. However, the abstraction is incomplete and even in the class you must follow some conventions or your code will break at runtime.

Here is a simple example of a Vuex module class, taken from the official documentation:

import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators";
import { get } from "~/services/request";

@Module({ name: "vehicle", stateFactory: true, namespaced: true })
export default class VehicleModule extends VuexModule {
  wheels: number = 2;

  get axles(): number {
    return this.wheels / 2;
  }

  @Mutation
  puncture(n: number): void {
    this.wheels = this.wheels - n;
  }

  @Mutation
  addWheel(n: number) {
    this.wheels = this.wheels + n;
  }

  @Action({ rawError: true })
  async fetchNewWheels(wheelStore: string): Promise<void> {
    const wheels = await get(wheelStore);
    this.addWheel(wheels);
  }
}

It shows the four basic Vuex concepts and how they are represented in a module class:

These are also the only types of class members supported. If you try to add an undecorated method, you will run into problems. Let us say I want to add a helper method to be called by an action:

@Action({ rawError: true })
async fetchNewWheels(wheelStore: string): Promise<void> {
  if (!this.validate(wheelStore)) {
    return
  }
  const wheels = await get(wheelStore)
  this.addWheel(wheels)
}

validate(_wheelStore: string): boolean {
  return true
}

Although the code compiles without any problems, it fails at runtime with the following error:

this.validate is not a function

This happens because the action methods in the module class are called with a rebound this value, as documented:

Once decorated with @Action the function will be called with this having the following shape - {...[all fields of state], context}.

The rebound this value will actually contain more than what is listed above, namely all the standard Vuex members (fields of state, getters, mutations and actions), along with the Vuex context and store objects. However, all other class members (such as the validate method above) will not be included and therefore will not be accessible:

This in action methods

In mutation methods, the rebound this value is even more limited. It only contains the Vuex fields of state:

This in mutation methods

Despite all this, there is a way to reuse some of the code from multiple actions. It only has to be placed in a function outside the class:

function validate(_wheelStore: string): boolean {
  return true
}

@Module({ name: 'vehicle', stateFactory: true, namespaced: true })
export default class VehicleModule extends VuexModule {
{
  // ...
  @Action({ rawError: true })
  async fetchNewWheels(wheelStore: string): Promise<void> {
    if (!validate(wheelStore)) {
      return
    }
    const wheels = await get(wheelStore)
    this.addWheel(wheels)
  }
}

If you need access to state in this function, you must pass it as a function parameter.

You can find a sample Nuxt application that demonstrates the above behavior in my GitHub repository.

Although the Vuex module decorators give the illusion that the Vuex module code is encapsulated in a class, it is still limited to only the standard Vuex concepts: state, getters, mutations, and actions. All other members of the class are inaccessible at runtime. However, Vuex module decorators still provide a lot of value with type-safe access in TypeScript.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License