Previous post: Vue & TypeScript First Taste

In my last article, I briefly introduced the use of TypeScript in Vue projects and some of the changes to the way Vue components are written. This article focuses on how to write Vuex in TypeScript more closely to TS object-oriented programming.

options

A quick check shows that there are currently two libraries available for Vue typescript

  • vuex-class
  • vuex-module-decorators

Vuex-class was last updated 8 months ago and is not updated frequently, so I chose vuex-Module-decorators for learning & validation

Install vuex – module – decorators

Championswimmer. In /vuex-module…

npm install -D vuex-module-decorators
Copy the code

or

yarn install vuex-module-decorators
Copy the code

Babel 6/7

In the project created by @vue/ CLI 3.0, @vue/babel-preset-app already contains the Babel item plug-in.

npm install babel-plugin-transform-decorators --dev
Copy the code

or

yarn install babel-plugin-transform-decorators --dev
Copy the code

TypeScript (3 x) configuration (tsconfig. Json)

In tsconfig.json you need to set:

experimentalDecorators: true,
importHelpers: true
Copy the code

EmitHelpers: true is required in TypeScript 2

configuration

Starting with the [email protected] release, the code is finally released in ES5 format, so the following sections focus on v0.9.2 and earlier

The package eventually generates code in ES2015(ES6) format, so if your Vue project eventually generates ES6, nothing needs to be done. However, if it is ES5(for compatibility with older browsers such as IE9, IE10, IE11), then in the project created via @vue/ CLI, the following configuration needs to be added for conversion:

// in your vue.config.js
module.exports = {
  /* ... other settings */
  transpileDependencies: ['vuex-module-decorators']}Copy the code

Results show

After using the library, writing Vuex Modules can look like this:

// eg. /app/store/posts.ts
import {VuexModule, Module} from 'vuex-module-decorators'
import {get} from 'axios'

@Module
export default class Posts extends VuexModule {
    posts: PostEntity[] = [] // initialise empty for now

    get totalComments (): number {
        return posts.filter((post) = > {
            // Take those posts that have comments
            return post.comments && post.comments.length
        }).reduce((sum, post) = > {
            // Sum all the lengths of comments arrays
            return sum + post.comments.length
        }, 0)
    }
    @Mutation
    updatePosts(posts: PostEntity[]) {
        this.posts = posts
    }

    @Action({commit: 'updatePosts'})
    async function fetchPosts() {
        return await get('https://jsonplaceholder.typicode.com/posts')}}Copy the code

It is equivalent to:

// equivalent eg. /app/store/posts.js
module.exports = {
  state: {
    posts: []},getters: {
    totalComments: (state) = > {
      return state.posts
        .filter((post) = > {
          return post.comments && post.comments.length
        })
        .reduce((sum, post) = > {
          return sum + post.comments.length
        }, 0)}},mutations: {
    updatePosts: (state, posts) = > {
      // 'posts' is payload
      state.posts = posts
    }
  },
  actions: {
    fetchPosts: async (context) => {
      // the return of the function is passed as payload
      const payload = await get('https://jsonplaceholder.typicode.com/posts')
      // the value of 'commit' in decorator is the mutation used
      context.commit('updatePosts', payload)
    }
  }
}
Copy the code

Benefits of type safety

To take advantage of type safety, you cannot dispatch/commit in the usual way… , such as:

MIT (store.com'updatePosts'Await store. Dispatch ('fetchPosts')Copy the code

Writing this way, you don’t take advantage of type safety features, and the IDE doesn’t provide help tips, etc. The best way is to use the getModule accessor to take advantage of the new type safety mechanism:

import { getModule } from 'vuex-module-decorators'Import Posts from '~/store/posts.js' const postsModule = getModule(Posts) // Access the Posts module const Posts = postsModule / / using getters const commentCount = postsModule. TotalComments / / commit mutation postsModule. UpdatePosts (newPostsArray) / /  dispatch action await postsModule.fetchPosts()Copy the code

Begin to use

Define a Module

To define a modules, you create a class that extends to VuexModule, and the Module decorates it.

// eg. src/store/MyModule.ts
import { Module, VuexModule } from 'vuex-module-decorators'

@Module
export default class MyModule extends VuexModule {
  someField: string = 'somedata'
}
Copy the code

❌import {Module} from ‘vuex’ ✔️ import {Module} from ‘vuex-mole-decorators’

2. Use it in store

In your Store, you can use the class MyModule as a Module

// src/store/index.ts

import Vue from 'vue'
import Vuex from 'vuex'

import MyModule from './MyModule'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},mutations: {},actions: {},modules: {
    myMod: MyModule
  }
})
Copy the code

Note: The way we use the MyModule class differs from classic object-oriented programming, similar to how Vue-class-Component works. Use the class itself as a module, rather than an object constructed by the class. New MyModule() ❌

3. Access to the state

Direct access through the Store

Import The store
import store from '~/store'

store.state.myMod.someField
Copy the code

Used in componentsthis.$storeaccess

this.$store.state.myMod.someField
Copy the code

4. Use getModule() to access the Store securely

In addition, for more type-safe access, we can use getModule() to modify the file we wrote earlier:

  • src/store/index.ts
  • src/store/MyModule.ts
  • src/views/useVuex.vue
// src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},mutations: {},actions: {},modules: {}})Copy the code

Note: SRC /store/index.ts does not define any Module

// src/store/MyModule.ts
import { Module, VuexModule } from 'vuex-module-decorators'
import store from './index'

@Module({ dynamic: true, store, name: 'mymod' })
export default class MyModule extends VuexModule {
  someField: string = 'somedata'
}
Copy the code
// src/views/useVuex.vue
<template>
  <div>
    {{hello}}
  </div>
</template>

<script lang='ts'>
import { Vue, Component, Emit, Inject, Model, Prop, Provide, Ref, Watch, PropSync } from 'vue-property-decorator'
import { Module, VuexModule, getModule } from 'vuex-module-decorators'

import MyModule from '@/store/MyModule'

const $myMod = getModule(MyModule) // Retrieve a module from the Store

@Component
export default class UseVuex extends Vue {
  hello: string = $myMod.someField
}
</script>
Copy the code

The core

1. state

All properties defined in a class are defined as state properties. For example:

import { Module, VuexModule } from 'vuex-module-decorators'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2
}
Copy the code

Is equivalent to:

export default {
  state: {
    wheels: 2}}Copy the code

2. Getters

All get functions are converted to vuex getters, such as:

import { Module, VuexModule } from 'vuex-module-decorators'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2
  // Get axles will be converted to vuex getters
  get axles() {
    return wheels / 2}}Copy the code

Is equivalent to:

export default {
  state: {
    wheels: 2
  },
  getters: {
    axles: (state) = > state.wheels / 2}}Copy the code

3. Mutations

All functions decorated by the @mutation decorator, will be converted to Vuex mutations. Such as:

import { Module, VuexModule, Mutation } from 'vuex-module-decorators'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2

  @Mutation
  puncture(n: number) {
    this.wheels = this.wheels - n
  }
}
Copy the code

Is equivalent to:

export default {
  state: {
    wheels: 2
  },
  mutations: {
    puncture: (state, payload) = > {
      state.wheels = state.wheels - payload
    }
  }
}
Copy the code

Pay attention to

Once a function is decorated with @mutation, the this context within the function points to the current state. Therefore, if you want to change the value of some property in state, you can simply write this.item++ instead of the original state.item++

warning

The Muation function cannot be an async function, nor can it be defined using the arrow function, because the context in which the code needs to be rebound during run execution.

4. Actions

All functions decorated by the @Actions decorator are converted to vuex Actions. Such as:

import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
import { get } from 'request'

@Module
export default class Vehicle extends VuexModule {
  wheels = 2

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

  @Action
  async fetchNewWheels(wheelStore: string) {
    const wheels = await get(wheelStore)
    this.context.commit('addWheel', wheels)
  }
}
Copy the code

Is equivalent to

const request = require('request')
export default {
  state: {
    wheels: 2
  },
  mutations: {
    addWheel: (state, payload) = > {
      state.wheels = state.wheels + payload
    }
  },
  actions: {
    fetchNewWheels: async (context, payload) => {
      const wheels = await request.get(payload)
      context.commit('addWheel', wheels)
    }
  }
}
Copy the code

Pay attention to

This.context.com MIT (‘mutationName’, mutPayload)

warning

  • If you need to run a lengthy task/function in the action function, you are advised to define the task as an asynchronous function. Even if you don’t, the library wraps your function in a Promise and waits for its execution to finish.
  • If you insist on using synchronous functions to perform tasks, use them insteadMutation
  • Never use the (=>) arrow function to define the action function, because the this context needs to be dynamically bound at runtime.

Advanced usage

1. Namespaces

Before reading this section, what is the vuex namespace

If you want to use modules as namespaces, add an extra argument to the @Module decorator. For example, the following example code adds a Module with Namespaced for MM

@Module({ namespaced: true.name: 'mm' })
class MyModule extends VuexModule {
  wheels = 2

  @Mutation
  incrWheels(extra: number) {
    this.wheels += extra
  }

  get axles() {
    return this.wheels / 2}}const store = new Vuex.Store({
  modules: {
    mm: MyModule
  }
})
Copy the code

Pay attention to

The @Module decorator property field name value must be the same as the new store({modules: {}}) registered Module name.

Keeping these two the same by hand would make coding inelegant, but this is important. We have to convert the: this.store.dispatch (‘action’) call to this.store.dispatch (‘name/action’), and we need to use the correct name in the decorator for it to work

2. Dynamic module

Before reading this section, it is necessary to understand vuex-dynamic-Modulo-registration

Creating a dynamic Module is as simple as passing a few more arguments to the @Module decorator, but only if you first create a Store object and pass it to the Module.

1) create a Store

// @/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  /* If all modules are created dynamically, this parameter is null */
})
Copy the code

2) Create a dynamic Module

// @/store/modules/MyModule.ts
import store from '@/store' // Notice the store instance
import {Module, VuexModule} from 'vuex-module-decorators'

@Module({dynamic: true, store, name: 'mm'})
export default class MyModule extends VuexModule {
  /* Your module definition as usual */
}
Copy the code
Pay attention to

As of now, vuex-module-decorators do not support dynamically nested modules

important

Make sure the store instance is created when you import/required. It must be created before the dynamic Module.

It is important that the Store instance be created in advance. Because the Store instance will be passed as a parameter to the @Module decorator, dynamic Modules will be registered successfully.

A link to the

  • github: vue-typescript-skills
  • Vue & TypeScript first experience
  • vuex-module-decorators
  • Vuex-module – Decorators Use case:
    • Github.com/Armour/vue-…
    • Github.com/xieguangcai…
    • Github.com/coding-bloc…
  • vuex – modules
  • vuex – dynamic-module-registration