vuex-module-decorators介绍和源码解读

1,465 阅读14分钟

1、 前言

看到公司框架中的vuex使用的是vuex-module-decorators扩展模块。使用对象式的调用方式调用vuex的State、Getter、Mutation、Action。好奇实现方式,翻了翻vuex-module-decorators的源码,写一篇文章记录一下vuex-module-decorator的使用方式和个人的一些理解,如果有理解错误的地方希望大家可以指正。

本文会为大家介绍vuex-module-decorators的使用方式,让大家在开发过程中通过vuex-module-decorators更丝滑的使用vuex。

再为大家撕一遍vuex-module-decorators的源码,了解一下vuex-module-decorators的实现方式和思路。让大家在使用过程中避免一些可能遇到的问题,并且在遇到问题的时候可以更方便的定位和解决问题。

希望大家通过阅读本文中的内容可以对vuex和vuex-module-decorators有更深的了解。

2 vuex-module-decorators是什么?

vuex-module-decorators是vuex的一个扩展模块,可以让你像调用对象的方式一样操作vuex,提供类型安全的类型校验和在IDE中自动补全/提醒/快捷定位方法功能。官网地址

4 vuex-module-decorators简介

vuex-module-decorators模块是在vuex原始结构上面,把State、Getter、Mutation、Action、命名空间、动态模块进行了重新封装。 在编写代码的时候像写一个类那样去写vuex。在使用的时候像使用对象那样去使用vuex。

4.1 vuex-module-decorators和vuex定义风格对比
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')
  }
}

转换成vuex风格代码

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)
    }
  }
}
调用方式对比
获取state

vuex-module-decorators调用

UserModule.userCode

vuex调用

 store.state[name?].userCode
获取getter

vuex-module-decorators调用

SappModule.currentTab

vuex调用

store.getters.[name/?]currentTab
调用Mutation

vuex-module-decorators调用

SappModule.SET_JDEID(eid)

vuex调用

store.commit([name/?]SET_JDEID, eid)
调用Action

vuex-module-decorators调用

SappModule.getMenus()

vuex调用

store.dispatch([name/?]getMenus)
4.2 vuex-module-decorators中定义vuex的状态(State)

vuex-module-decorators定义的类中所有属性都转换为vuex的状态(state)。

官方State文档

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

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

相当于:

export default {
  state: {
    wheels: 2
  }
}
4.3 vuex-module-decorators中定义vuex的Getter

vuex-module-decorators定义的类中所有 ES6 getter 函数都转换为 vuex getter。

官方Getter文档

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

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

相当于:

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

带参数的getter定义(Method-Style Access):

@Module
export default class Vehicle extends VuexModule {
  companies = []
  get company() {
    return (companyName: string) => { this.companies.find(company => company.name === companyName) };
  }
}
4.4 vuex-module-decorators中定义vuex的Mutation

所有用 修饰的函数@Mutation修饰的方法都转换为 Vuex 的mutation。 官方Mutation文档

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

相当于:

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

用@Mutation装饰器装饰Mutations方法在运行时,会将this设置为state,所以想改变state中的状态:state.item++,很简单this.item++

4.5 vuex-module-decorators中定义vuex的Action

所有装饰的函数@Action修饰的方法都转换为 vuex的action  官方Action文档

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

相当于:

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

用@Action函数装饰的方法,在运行的时候,被调用的this将具有以下形状 -{...[all fields of state], context}。因此,要从动作的主体内手动提交mutation,只需调用this.context.commit('mutationName', mutPayload)。 PS:下文介绍源码的时候会有介绍this

4.6 vuex-module-decorators装饰器:MutationActions

除了上面介绍的state、getter、mutation、action之外,vuex-module-decorators模块还支持另外一种定义为:MutationActions的装饰器 MutationActions。 MutationActions装饰器会经历以下过程

1.先调用一个action,执行异步脚本。 2.然后再讲action中返回的值,通过commit将结果值提交到store中。

vuex-module-decorators写法

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

相当于:

const typicodeModule = {
  state: {
    posts: [],
    users: []
  },
  mutations: {
    updatePosts: function (state, posts) {
      state.posts = posts
    }
  },
  actions: {
    updatePosts: async function (context) {
      const posts = await axios.get('https://jsonplaceholder.typicode.com/posts')
      context.commit('updatePosts', posts)
    }
  }
}
4.7 vuex-module-decorators中定义vuex的命名空间(Namespaced Modules)

在Class装饰器@Module中通过{ namespaced: true }指定命名空间(Namespaced Modules)

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

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

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

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

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

装饰器中的name字段应与您在创建store时将分配给模块的实际名称相匹配。

{ root: true } 这样虽然在命名空间模块中,但将通过dispatch调用clear方法的时候不需要使用dispatch('mm/clear')进行调用

4.8 vuex-module-decorators中定义vuex的动态模块(Dynamic Modules)

vuex-module-decorators可以简单地通过向@Module装饰器向类传递一些属性{dynamic: true, store, name: 'mm'}来动态注册模块 该过程的一个重要部分是,我们必须首先创建商店,然后将商店传递给模块。Dynamic Modules

第 1 步:创建商店

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

步骤 2:创建动态模块

// @/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
  */
}

5 vuex-module-decorators源码介绍

5.1 vuex-module-decorators源码结构
module文件夹:@Module装饰器的处理过程
action文件:@Action装饰器的处理过程
config文件:一些公共的配置(原始错误:rawError)
helper文件:一些公关扩展方法
index文件:入口文件,会导出vuex-module-decorators提供的一些装饰器(ModuleActionMutationMutationAction)、一些基础类型定义(VuexModule)、一些扩展方法(getModule)、公共定义(config)
moduleoptions文件:ts接口定义文件
mutation文件:@Mutation装饰器处理过程
mutationaction文件:@MutationAction装饰器处理过程
vuexmodule文件:getModule方法处理过程

5.2 vuex-module-decorators原理

通过处理类中定义的属性、getter、@Action修饰器修饰的方法、@Mutation修饰器修饰的方法、@MutationAction修饰器修饰的方法、@Module修饰器修饰的类,包装成vuex的state、getter、action、mutation结构,通过Vuex.Store()或者使用registerModule动态注册成vuex的模块 如果是具名对象(在@Module修饰器中传入name属性),在通过getModule返回的对象中,可以通过访问类的值/方法的时候返回vuex的state/使用commit、dispatch调用vuex的action和mutation

5.3 vuex结构配置
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
5.4 action.ts文件

通过@Action装饰器,包装类方法为vuex调用的时候的action结构,并且保存到类的actions中

/*
 * action装饰器,把方法挂载到类的actions属性上面
 * @param params
 * @returns
 */
function actionDecoratorFactory(params) {
  const { commit = undefined, rawError = !!config.rawError, root = false } = params || {}
  return function(target, key, descriptor) {
    // 获取当前类的constructor
    const module = target.constructor
    // 判断当前constructor中是否有定义actions属性
    if (!module.hasOwnProperty('actions')) {
      // 收集actions属性中定义的对象(支持类中直接定义actions属性的写法)
      module.actions = Object.assign({}, module.actions)
    }
    // 获取@Action修饰器修饰的方法
    const actionFunction = descriptor.value
    // ************************************************************************
    // 重新生成一个方法挂载到action上面,vuex中action直接执行的时候调用该方法,入参为vuex的context
    const action = async function(context, payload) {}
    // ************************************************************************
    // 如果是根节点,此处保存成一个包含root和handler的方法,否则只有一个方法
    module.actions[key] = root ? { root, handler: action } : action
  }
}
/**
 * The @Action decorator turns an async function into an Vuex action
 *
 * @param targetOrParams the module class
 * @param key name of the action
 * @param descriptor the action function descriptor
 * @constructor
 */
export function Action(targetOrParams, key, descriptor) {
  if (!key && !descriptor) {
    return actionDecoratorFactory(targetOrParams)
  } else {
    actionDecoratorFactory()(targetOrParams, key, descriptor)
  }
}

action方法封装

当vuex的action方法被触发的时候,会调用vuex-module-decorators封装的类的方法,并且把vuex的context绑定到对module类的this中(代码中的:moduleAccessor/thisObj对象),可以通过this.context来进行访问

const action = async function(context, payload) {
      try {
        let actionPayload = null

        // 是否已经处理过的对象,处理过的对象有_genStatic属性
        if (module._genStatic) {
          const moduleName = getModuleName(module)
          // 查找是否为rootGetters上面的值,否则。重新获取Module
          const moduleAccessor = context.rootGetters[moduleName] ? context.rootGetters[moduleName] : getModule(module)
          // 挂载context到moduleAccessor中,moduleAccessor为getModule获取到的值
          moduleAccessor.context = context
          // 执行该方法的,获取方法return的值,方法中的对象为context
          actionPayload = await actionFunction.call(moduleAccessor, payload)
        } else {
          const thisObj = { context }
          addPropertiesToObject(thisObj, context.state)
          addPropertiesToObject(thisObj, context.getters)
          actionPayload = await actionFunction.call(thisObj, payload)
        }
        // 是否存在commit,如果存在commit,则执行commit配置的mutation,入参为action的值
        if (commit) {
          context.commit(commit, actionPayload)
        }
        // 返回结果
        return actionPayload
      } catch (e) {}
    }

_genStatic:如果装饰器@Module定义了中name属性,会生成_genStatic变量 如果_genStatic存在,此时执行的actionFunction中的this,指向的是getModule获取到的值+context。此时支持在@Action修饰的方法中直接调用@Mutation修饰的方法 如果_genStatic不存在,此时actionFunction中的this包括state、getters中的值+context。此时不支持在@Action修饰的方法中直接调用@Mutation修饰的方法

在运行时得到的actions结构截图

5.5 mutation.ts文件

通过@Mutation装饰器,包装当前方法为vuex调用的时候的mutation结构,并且保存到类的mutations中

/**
 * Mutation装饰器,把方法挂载到类的mutations属性上,注册vuex的时候mutations会直接取mutations的值
 * @param target
 * @param key
 * @param descriptor
 */
export function Mutation(target, key, descriptor) {
  // 获取当前类的constructor
  const module = target.constructor
  // 判断当前constructor中是否有定义mutations属性
  if (!module.hasOwnProperty('mutations')) {
    // 收集mutations属性中定义的对象(支持类中直接定义mutations属性的写法)
    module.mutations = Object.assign({}, module.mutations)
  }
  // 获取@Mutation修饰器修饰的方法
  const mutationFunction = descriptor.value
  // 把方法挂载到类的mutations属性上,并且在执行方法的时候,把state作为第一个入参
  const mutation = function(state, payload) {
    // mutationFunction在执行的时候this指向state
    mutationFunction.call(state, payload)
  }
  module.mutations[key] = mutation
}
mutationFunction.call(state, payload),mutationFunction在调用的时候其中的this指向vuex的state,所以在mutation中只能通过this访问定义的属性,无法访问module类的其他方法
const mutationFunction = descriptor.value
// 把方法挂载到类的mutations属性上,并且在执行方法的时候,把state作为第一个入参
const mutation = function(state, payload) {
  mutationFunction.call(state, payload)
}

在运行时得到的mutations结构截图

5.6 module文件夹

把@Module修饰的类的属性封装成state格式 把get封装成getters格式 把@Mutation装饰器装饰的方法封装成mutations格式 把@Action装饰器装饰的方法封装成actions格式 使用registerModule注册vuex的动态模块 通过_genStatic拼装可以直接通过访问对象一样访问vuex的对象

/**
 * module装饰器
 * @param {*} modOrOpt
 * @returns
 */
export function Module(modOrOpt) {
  if (typeof modOrOpt === 'function') {
    /*
     * @Module decorator called without options (directly on the class definition)
     */
    moduleDecoratorFactory({})(modOrOpt)
  } else {
    /*
     * @Module({...}) decorator called with options
     */
    return moduleDecoratorFactory(modOrOpt)
  }
}
moduleDecoratorFactory:@Module修饰符修饰的类处理方法
/**
 * class的module装饰器
 * @param moduleOptions
 * @returns
 */
function moduleDecoratorFactory(moduleOptions) {
  return function(constructor) {
    const module = constructor
    // state处理方法
    // ************************************************************************
    const stateFactory = () => sf(module)
    // ************************************************************************

    // 收集module的状态赋值到state
    if (!module.state) {
      module.state = moduleOptions && moduleOptions.stateFactory ? stateFactory : stateFactory()
    }
    // 收集所有的getters
    if (!module.getters) {
      module.getters = {}
    }
    // 命名空间
    if (!module.namespaced) {
      module.namespaced = moduleOptions && moduleOptions.namespaced
    }

    let parentModule = Object.getPrototypeOf(module)
    // 是否有继承其他的类
    while (parentModule.name !== 'VuexModule' && parentModule.name !== '') {
      // 把parentModule中的getter拷贝到module上面
      addGettersToModule(module, parentModule)
      parentModule = Object.getPrototypeOf(parentModule)
    }
    // ************************************************************************
    // 拷贝类自身get标识的方法到getters
    addGettersToModule(module, module)
    // ************************************************************************
    const modOpt = moduleOptions
    if (modOpt.name) {
      // ************************************************************************
      // 为对象定义_genStatic变量,保存数据,getModule中会获取
      Object.defineProperty(constructor, '_genStatic', {})
      // ************************************************************************

      // 为对象添加名称属性,后续getModuleName会用到
      Object.defineProperty(constructor, '_vmdModuleName', {
        value: modOpt.name
      })
    }

    if (modOpt.dynamic) {
      // ************************************************************************
      // 注册vue异步store模块
      registerDynamicModule(module, modOpt)
      // ************************************************************************
    }
    return constructor
  }
}

在运行时得到的state和getters结构截图

sf => stateFactory 复制module类中的属性封装成vuex的state结构

保留关键字
const reservedKeys = ['actions', 'getters', 'mutations', 'modules', 'state', 'namespaced', 'commit']
// 收集所有的状态
export function stateFactory(module) {
  // 获取全部的属性列表
  const state = new module.prototype.constructor({})
  const s = {}
  Object.keys(state).forEach((key) => {
    // 过滤保留关键字
    if (reservedKeys.indexOf(key) !== -1) {
      if (typeof state[key] !== 'undefined') {
        throw new Error(
          `ERR_RESERVED_STATE_KEY_USED: You cannot use the following
        ['actions', 'getters', 'mutations', 'modules', 'state', 'namespaced', 'commit']
        as fields in your module. These are reserved as they have special purpose in Vuex`
        )
      }
      return
    }
    // 判断是否有这个值,且不为function,此处收集到s中
    if (state.hasOwnProperty(key)) {
      if (typeof state[key] !== 'function') {
        s[key] = state[key]
      }
    }
  })

  return s
}

addGettersToModule 复制module类中的get/getters装饰的方法到vuex的getters结构 获取srcModule的prototype中的所有的属性名称列表 判断属性的描述对象中是否有get/getters方法 封装有get/getters方法到vuex的getters结构中

/**
 * 拷贝srcModule对象中的getters到targetModule中
 * @param targetModule
 * @param srcModule
 */
function addGettersToModule(targetModule, srcModule) {
  Object.getOwnPropertyNames(srcModule.prototype).forEach((funcName) => {
    // 获取指定的属性信息
    // configurable: true
    // enumerable: false
    // value: ƒ Sapp()
    // writable: true
    const descriptor = Object.getOwnPropertyDescriptor(
      srcModule.prototype,
      funcName
    )
    // 判断当前的对象是否有get方法,如果有,添加到targetModule的getters上面
    if (descriptor.get && targetModule.getters) {
      // 封装成vuex的getter结构
      targetModule.getters[funcName] = function(state, getters, rootState, rootGetters) {
        const thisObj = { context: { state, getters, rootState, rootGetters } }
        addPropertiesToObject(thisObj, state)
        addPropertiesToObject(thisObj, getters)
        // get的方法中的this包含:state中的值getters中的值,context
        const got = (descriptor.get).call(thisObj)
        return got
      }
    }
  })
}

registerDynamicModule

通过上面state、getters、@Action、@Mutation处理过以后,得到vuex的module结构,异步注册组件到vuex中

/**
 * 注册异步组件
 * @param module
 * @param modOpt
 */
function registerDynamicModule(module, modOpt) {
  if (!modOpt.name) {
    throw new Error('Name of module not provided in decorator options')
  }

  if (!modOpt.store) {
    throw new Error('Store not provided in decorator options when using dynamic option')
  }

  modOpt.store.registerModule(
    modOpt.name, // TODO: Handle nested modules too in future
    module,
    { preserveState: modOpt.preserveState || false } // 是否保留之前的state
  )
}

@Module处理以后返回的对象截图

5.6 vuexmodule.ts

getModule方法的处理,返回可以通过访问对象属性/方法一样访问vuex的state、getter、action、mutation

import { getModuleName } from './helpers'

/**
 * 获取module对象,可以直接调用方法,触发vuex的调用
 * @param moduleClass
 * @param store
 * @returns
 */
export function getModule(moduleClass, store) {
  // 获取当前类的name
  const moduleName = getModuleName(moduleClass)
  // getters上是否存在同名的方法
  if (store && store.getters[moduleName]) {
    return store.getters[moduleName]
  } else if (moduleClass._statics) { // 是否为静态绑定,是否已绑定过,用来缓存,防止重置执行
    return moduleClass._statics
  }

  // ************************************************************************
  // 获取moduleDecoratorFactory中添加到对象上面的变量,会返回一个包含store的方法
  const genStatic = moduleClass._genStatic
  // ************************************************************************
  if (!genStatic) {
    throw new Error(`ERR_GET_MODULE_NO_STATICS : Could not get module accessor.
      Make sure your module has name, we can't make accessors for unnamed modules
      i.e. @Module({ name: 'something' })`)
  }

  // storeModule中保存了vuex的state、getters、mutation、action、store对象。
  // storeModule中的属性在访问的时候get被劫持指向vuex的state、getters。方法被指向的时候指向vuex的mutation、action
  const storeModule = genStatic(store)

  if (store) {
    store.getters[moduleName] = storeModule
  } else {
    // 存储到类的_statics属性里面,后续使用
    moduleClass._statics = storeModule
  }

  return storeModule
}

5.8 getModule方法获取的对象截图

5.9 _genStatic方法

拼接store到statics中
通过staticStateGenerator方法,挂载vuex的state到statics对象的属性上
通过staticGetterGenerator方法,挂载vuex的getters到statics对象的属性上
通过staticMutationGenerator方法,挂载vuex的mutations到statics对象的属性上
通过staticActionGenerators方法,挂载vuex的actions到statics对象的属性上
返回一个拥有store、vuex的state属性列表、vuex的getter属性列表、vuex的mutations方法列表、vuex的actions方法列表的对象
      // 为对象定义_genStatic变量,保存数据,getModule中会获取
      Object.defineProperty(constructor, '_genStatic', {
        value: (store) => {
          // 如果有传入store,此处直接使用store,否则使用Module装饰器获取到的store
          const statics = { store: store || modOpt.store }
          if (!statics.store) {
            throw new Error(`ERR_STORE_NOT_PROVIDED: To use getModule(), either the module
            should be decorated with store in decorator, i.e. @Module({store: store}) or
            store should be passed when calling getModule(), i.e. getModule(MyModule, this.$store)`)
          }
          // ===========  For statics ==============
          // ------ state -------,挂载module的state到statics中
          staticStateGenerator(module, modOpt, statics)
          // ************************************************************************

          // ------- getters -------
          if (module.getters) {
            // 挂载module的getters到statics,此处挂载以后SappModule.xxx可以直接取到statics.store.getters中的值
            staticGetterGenerator(module, modOpt, statics)
            // ************************************************************************
          }

          // -------- mutations --------
          if (module.mutations) {
            // 挂载module的Mutation到statics,此处挂载以后SappModule.xxx()可以直接取调用statics.store.commit
            staticMutationGenerator(module, modOpt, statics)
            // ************************************************************************
          }
          // -------- actions ---------
          if (module.actions) {
            // 挂载module的Action到statics,此处挂载以后SappModule.xxx()可以直接取调用statics.store.dispatch
            staticActionGenerators(module, modOpt, statics)
            // ************************************************************************
          }
          return statics
        }
      })

_genStatic返回结构截图

5.10 staticStateGenerator方法

state可以是一个方法、也可以是一个对象,现在使用的一般都是对象

statics中的属性被访问的时候,去store中的state中查找相应属性的值 如.果存在name,则去获取对应对象下的state

/**
 * 获取模块的state挂载到statics
 * @param module
 * @param modOpt
 * @param statics
 */
export function staticStateGenerator(module, modOpt, statics) {
  // 获取当前模块的state
  const state = modOpt.stateFactory ? module.state() : module.state
  Object.keys(state).forEach((key) => {
    if (state.hasOwnProperty(key)) {
      // If not undefined or function means it is a state value,不挂载undefined和function
      if (['undefined', 'function'].indexOf(typeof state[key]) === -1) {
        // 代理对象的属性到vuex的state中
        Object.defineProperty(statics, key, {
          get() {
            const path = modOpt.name.split('/')
            let data = statics.store.state
            for (const segment of path) {
              data = data[segment]
            }
            return data[key]
          }
        })
      }
    }
  })
}

staticStateGenerator 执行结果

5.11 staticGetterGenerator方法

statics中的属性被访问的时候,如果是查找的store中的getters,去store中的getters中查找相应属性的值,如果有namespaced,则去查找name/key。

/**
 * 挂载module的getters到statics,此处挂载以后SappModule.xxx可以直接取到statics.store.getters中的值
 * @param module
 * @param modOpt
 * @param statics
 */
export function staticGetterGenerator(module, modOpt, statics) {
  // 挂载statics的key到store的getters中
  Object.keys(module.getters).forEach((key) => {
    if (module.namespaced) {
      Object.defineProperty(statics, key, {
        get() {
          return statics.store.getters[`${modOpt.name}/${key}`]
        }
      })
    } else {
      Object.defineProperty(statics, key, {
        get() {
          return statics.store.getters[key]
        }
      })
    }
  })
}

staticGetterGenerator的执行结果运行截图

5.12 staticMutationGenerator方法

statics中的方法被访问的时候,如果是查找的store中的mutations,去store中的commit提交对应的mutation,如果有namespaced,则去查找name/key。

/**
 * 挂载module的Mutation到statics,此处挂载以后SappModule.xxx()可以直接取调用statics.store.commit
 * @param module
 * @param modOpt
 * @param statics
 */
export function staticMutationGenerator(module, modOpt, statics) {
  // 获取Mutations装饰器挂载到mutations中的列表
  Object.keys(module.mutations).forEach((key) => {
    if (module.namespaced) {
      // 此处获取到的方法可以直接执行,相当于直接执行方法
      statics[key] = function(...args) {
        statics.store.commit(`${modOpt.name}/${key}`, ...args)
      }
    } else {
      statics[key] = function(...args) {
        statics.store.commit(key, ...args)
      }
    }
  })
}

staticMutationGenerator的运行结果截图

5.13 staticActionGenerators

statics中的方法被访问的时候,如果是查找的store中的actions,去store中的dispatch提交对应的action,如果有namespaced,则去查找name/key。

/**
 * 挂载module的Action到statics,此处挂载以后SappModule.xxx()可以直接取调用statics.store.dispatch
 * @param module
 * @param modOpt
 * @param statics
 */
export function staticActionGenerators(module, modOpt, statics) {
  Object.keys(module.actions).forEach((key) => {
    if (module.namespaced) {
      statics[key] = async function(...args) {
        return statics.store.dispatch(`${modOpt.name}/${key}`, ...args)
      }
    } else {
      statics[key] = async function(...args) {
        return statics.store.dispatch(key, ...args)
      }
    }
  })
}

staticActionGenerators的运行结果截图

getModule运行的结果截图 通过以上步骤就可以获取一个Module,Module的属性代理vuex的state/getter,Module中的方法代理vuex的mutation/action

6 vuex-module-decorators的调用和vuex的调用方式的对比

// 获取state
UserModule.userCode
statics.store.state[name?].userCode

// 获取getter
SappModule.currentTab
statics.store.getters.[name/?]currentTab

// 调用Mutation
SappModule.SET_JDEID(eid)
statics.store.commit([name/?]SET_JDEID, eid)

// 调用Action
SappModule.getMenus()
statics.store.dispatch([name/?]getMenus)

通过分析源代码,大家可以了解vuex-module-decorators的具体实现过程。通过装饰器,让vuex的state、getter、mutation、action调用方式代理到一个对象上面。 使用vuex的时候像使用对象的属性/方法一样丝滑。

优点:

在使用过程中调用方式简单方便。保留vuex的特性,比如:getModule中的属性都是响应式的。 并且可以类型安全的类型校验和在IDE中自动补全/提醒/快捷定位方法功能。

缺点:

改变了vuex的特性,可以直接对getModule获取的对象属性进行赋值。 在@Mutation装饰的方法中不允许调用其他的@Mutation,但是可以在@Action装饰的方法中调用其他的Mutation和Action,行为方式统一。

在不同类型装饰的方法中的this指向不同,再使用的时候可能会有歧义。

7 总结

vuex-module-decorators可以满足我们在日常开发中大部分使用vuex的场景,只要在使用的时候注意一下某些边界条件。可以大大提升我们的开发效率和开发体验。