英文使用说明地址​​https://championswimmer.in/vuex-module-decorators/​

1、安装

npm install vuex-module-decorators
# or
yarn add

2、概述

2.1 功能

这个库可以使用下面方式编写 ​​vuex​​ 模块

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

interface PostEntity {
comments: string[]
}

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

get totalComments(): number {
return this.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 fetchPosts() {
return get('https://jsonplaceholder.typicode.com/posts')
}
}

对应输出结果为

// 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,) => {
return sum + post.comments.length
}, 0)
}
},
mutations: {
updatePosts: (state,) => {
// '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)
}
}
}

2.2 类型安全的好处

而不是使用通常的方式来​​dispatch​​​和​​commit​

store.commit('updatePosts', posts)
await store.dispatch('fetchPosts')

它没有为有效负载提供类型安全,也没有在 IDE 中提供自动完成帮助, 您现在可以使用 ​​getModule​​ 访问器使用更多类型安全机制

import { getModule } from 'vuex-module-decorators'
import Posts from `~/store/posts.js`

const postsModule = getModule(Posts)

// access posts
const posts = postsModule.posts

// use getters
const commentCount = postsModule.totalComments

// commit mutation
postsModule.updatePosts(newPostsArray)

// dispatch action
await postsModule.fetchPosts()

3、开始

3.1 定义module

To define a module, create a class that extends from ​​VuexModule​​ and must be decorated with ​​Module​​ decorator

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

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

CAREFUL
There is a ​​​Module​​​ class in the ​​vuex​​ package too, which is not a
decorator. Make sure you import correct Module decorator from from
​​​vuex-module-decorators​​​ ❌ ​​import {Module} from 'vuex'​​ ✔️ ​​import {Module} from 'vuex-module-decorators'​

3.2 store内使用

In your store, you use the ​​MyModule​​ class itself as a module.

import Vuex from 'vuex'
import MyModule from '~/store/mymodule'

const store = new Vuex.Store({
modules: {
myMod: MyModule
}
})

NOTE
The way we use the MyModule class is different from classical object-oriented programming
and similar to how ​​​vue-class-component​​​ works.
We use the class itself as module, not an object constructed by the class
​​​new MyModule()​​ ❌

3.3 访问状态

All the usual ways of accessing the module works -

  1. Import The store
import store from '~/store'
store.state.myMod.someField
  1. Use​​this.$store​​ if in component
this.$store.state.myMod.someField

In addition to that, for a much more typesafe access, we can use ​​getModule()​

  1. Use​​getModule()​​ to create type-safe accessor
import { Module, VuexModule, getModule } from 'vuex-module-decorators'
import store from '@/store'

@Module({ dynamic: true, store, name: 'mymod' })
class MyModule extends VuexModule {
someField: number = 10
}
const myMod = getModule(MyModule)
myMod.someField //works
myMod.someOtherField //Typescript will error, as field doesn't exist

4、核心内容

4.1 State

All properties of the class are converted into state props.
For example, the following code

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

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

is equivalent of this -

export default {
state: {
wheels: 2
}
}

🚨 WARNING
If state value cannot be determined, it MUST be initialized with ​​null​​​. Just like ​​wheels: number | null = null​​.

4.2 Getters

All ES6 getter functions of the class are converted into vuex getters

For example, the following code -

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

@Module
export default class Vehicle extends VuexModule {
wheels = 2
get axles() {
return this.wheels / 2
}
}

is equivalent of this -

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

For Method-Style Access use vanilla vuex and return a function:

@Module
export default class Vehicle extends VuexModule {
companies = []
get company() {
return (companyName: string) => { this.companies.find(company => company.name === companyName) };
}
}

4.3 Mutations

All functions decorated with ​​@Mutation​​​ are converted into Vuex mutations
For example, the following code -

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
}
}

is equivalent of this -

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

NOTE
Once decorated with the ​​​@Mutation​​ decorator Mutations are run with this (context) set to the state
So when you want to change things in the state,
​​​state.item++​​​ is simply ​​this.item++​

🚨 WARNING
Mutation functions MUST NOT be async functions.
Also do not define them as arrow ➡️ functions, since we need to rebind them at runtime.

4.4 Actions

All functions that are decorated with ​​@Action​​​ are converted into
vuex actions.

For example this code -

import { Module, VuexModule, Mutation, Action } 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)
}
}

is equivalent of this -

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

NOTE
Once decorated with ​​​@Action​​​ the function will be called with ​​this​​​ having the following shape - ​​{...[all fields of state], context}​​ The action payload comes as an argument.
So to commit a mutation manually from within action’s body
simply call ​​this.context.commit('mutationName', mutPayload)

🚨️️ WARNING
If you are doing a long running task inside your action, it is recommended
to define it as an async function. But even if you do not, this library
will wrap your function into a Promise and await it.
If you want something to actually happen synchronously, make it a ​​Mutation​​​ instead
Also do not define them as arrow ➡️ functions, since we need to rebind them at runtime.

4.5 MutationActions

If you have understood how ​​Actions​​​ and ​​Mutations​​​ work
you might have requirements for some functions that -

  1. first do an asynchronous action
  2. and then commit the resultant value to the store via a mutation

This is where a ​​@MutationAction​​ comes to picture.

Here is a basic example

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

@Module
class TypicodeModule extends VuexModule {
posts: Post[] = []
users: User[] = []

@MutationAction
async function updatePosts() {
const posts = await axios.get('https://jsonplaceholder.typicode.com/posts')

return { posts }
}
}

That gets converted to something like this

const typicodeModule = {
state: {
posts: [],
users: []
},
mutations: {
updatePosts: function (state,) {
state.posts = posts
}
},
actions: {
updatePosts: async function (context) {
const posts = await axios.get('https://jsonplaceholder.typicode.com/posts')
context.commit('updatePosts', posts)
}
}
}

NOTE
Note that if S denotes the type of state, then the object returned from a
​​​MutationAction​​ function must of type Partial<S>
The keys present inside the return value (for eg, here ​​​posts​​​) are replaced into
the store.

NOTE
When a ​​​MutationAction​​​ function returns ​​undefined​​​, the mutation part of the
​​​MutationAction​​ will not be called, and the state will remain the same.

5、进阶教程

5.1 Namespaced Modules

TIP
Before reading this, it is imperative you understand what are
​​​namespaced modules​

If you intend to use your module in a namespaced way, then
you need to specify so in the ​​​@Module​​ decorator.

@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
}
})

NOTE
The ​​​name​​​ field in the decorator should match the actual name
that you will assign the module to, when you create the store.

It isn’t exactly elegant to manually keep these two same, but it
is important. We have to convert ​​​this.store.dispatch('action')​​​ calls into ​​this.store.dispatch('name/action')​​, and we need the
​name​​ to be correct in the decorator to make it work

5.1.1 Registering global actions inside namespaced modules

In order to ​​register actions of namespaced modules globally​​​ you can add a parameter ​​root: true​​​ to ​​@Action​​​ and ​​@MutationAction​​ decorated methods.

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

@Mutation
setWheels(wheels: number) {
this.wheels = wheels
}

@Action({ root: true, commit: 'setWheels' })
clear() {
return 0
}

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

const store = new Vuex.Store({
modules: {
mm: MyModule
}
})

This way the ​​@Action​clear of ​​MyModule​​​ will be called by dispatching ​​clear​​​ although being in the namespaced module ​​mm​​​.
The same thing works for ​​​@MutationAction​​​ by just passing ​​{ root: true }​​ to the decorator-options.

NOTE
When registering an action globally it can not be called by the namespace’s name.
For the example that means, that the action can not be called by dispatching ​​​mm/clear​​!

5.2 Dynamic Modules

tip
Before you read this secion, it is advised that you understand how
​​​dynamic module registration works​

Modules can be registered dynamically simply by passing a few properties into
the ​​​@Module​​​ decorator, but an important part of the process is, we first
create the store, and then pass the store to the module.

Step 1: Create the Store

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

const store = new Vuex.Store({
/*
Ideally if all your modules are dynamic
then your store is registered initially
as a completely empty object
*/
})

Step 2: Create the Dynamic Module

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

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

NOTE
As of now, we do not support dynamic + nested modules.

IMPORTANT ⛔️
Make sure your imports/requires are ordered in such a way that
the store definition is executed before the module class is created.

It is important for the store to exist, and be passed into the
​​​@Module​​ decorator for the module to get registered dynamically