[TOC]

简介

由于官方Vuex 3.x对于TypeScript的支持还较弱,因此本篇介绍 vuex-module-decorators 库的使用方法。

对于Vuex还不熟悉的,可以参考 Vuex官方文档
想查看更为细致的库使用方法,可以参考 vuex-module-decorators官方文档

使用理由

选用 vuex-module-decorators 的原因如下:

  • 采用class语法,与前面介绍的Vue的ts语法相似,心智负担较低,且使用起来也非常方便
  • 因为使用class语法,在ts支持方面有很好的表现,有着较为完善且使用方便的类型检查和语法提示
  • 可以很方便的支持vuex的模块化以及模块动态注册,项目结构更为清晰。

项目配置

// vue.config.js
module.exports = {
  // ... your other options
  transpileDependencies: [
    'vuex-module-decorators'
  ]
}

// tsconfig.json
{
  "compilerOptions": {
    "importHelpers": true,
    "experimentalDecorators": true
  }
}

Store 内声明

这里主要以vuex使用模块化为前提,使用模块化、命名空间、模块动态注册的情况下使用vuex。

目录结构

image.png
index.ts为veux的根模块。modules文件夹内放入各个模块的store

vue主入口 - main.ts

import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

这一步跟js没有什么区别,照常引入。

store主入口 - index.ts

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({})

利用该库,我们可以很方便的在各个vue文件内调用到各个模块的store,不需要根store。
且各模块都是使用的动态注册(用到时才引入),因此store主文件实例化时只需要传入一个空对象。

核心 - State

使用js:

export default {
  state: {
    userInfo: {
      prefixName: '哈利',
      suffixName: '波特',
      age: 18,
    }
  }
}

使用ts的写法为:

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

@Module({ name: 'user', namespaced: true, dynamic: true, store })
class User extends VuexModule {
  // state
  public userInfo = {
    prefixName: '哈利',
    suffixName: '波特',
    age: 18,
  }
}

export const UserModule = getModule(User)
  • 用法与vue组件中的 data 类似,直接在class中声明即可。

    核心 - Getters

    使用js:
    export default {
    state: {
      userInfo: {
        prefixName: '哈利',
        suffixName: '波特',
        age: 18,
      }
    },
    getters: {
        allName: (state) => `${state.userInfo.prefixName} · ${state.userInfo.suffixName}`
    },
    }
    
    使用ts的写法为: ```typescript import { Module, VuexModule, getModule } from ‘vuex-module-decorators’

@Module({ name: ‘user’, namespaced: true, dynamic: true, store }) class User extends VuexModule { // state public userInfo = { prefixName: ‘哈利’, suffixName: ‘波特’, age: 18, }

// getters public get allName() { return ${this.userInfo.prefixName} · ${this.userInfo.suffixName} } }

export const UserModule = getModule(User)


- getters类似于vue组件中的 `computed` 计算属性。
- 直接使用类中的 `get` 关键字表明这是一个getter。
- 若要访问state状态数据,直接通过 `this` 访问即可。
<a name="Z7n5X"></a>
## 核心 - Mutations
使用js:
```javascript
export default {
  state: {
    userInfo: {
      prefixName: '哈利',
      suffixName: '波特',
      age: 18,
    }
  },
  mutations: {
    SET_USER_AGE: (state, age) => {
      state.userInfo.age = age
    }
  }
}

使用ts的写法为:

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

@Module({ name: 'user', namespaced: true, dynamic: true, store })
class User extends VuexModule {
  // state
  public userInfo = {
    prefixName: '哈利',
    suffixName: '波特',
    age: 18,
  }

  // mutation
  @Mutation
  public SET_USER_AGE(age: number) {
    this.userInfo.age = age
  }
}

export const UserModule = getModule(User)
  • Mutation是更改Store中的状态(State)的唯一方式。
  • Mutation不允许异步操作。
  • js写法中的Mutation的第一个参数永远是当前store的状态state,用于访问及修改state。
  • 而ts的写法中不需要state参数,直接使用 this 访问和修改state即可。

    核心 - Actions

    使用js: ```javascript function getAge() { return new Promise(resolve => { setTimeout(() => {
    resolve(30)
    
    }, 2000) }) }

export default { state: { userInfo: { prefixName: ‘哈利’, suffixName: ‘波特’, age: 18, } }, mutations: { SET_USER_AGE: (state, age) => { state.userInfo.age = age } } actions: { getUserAge: async ({commit}) => { const age = await getAge() commit(‘SET_USER_AGE’, age) } }, }

使用ts的写法为:
```typescript
import { Module, VuexModule, getModule } from 'vuex-module-decorators'

function getAge(): Promise<number> {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(30)
    }, 2000)
  })
}

@Module({ name: 'user', namespaced: true, dynamic: true, store })
class User extends VuexModule {
  // state
  public userInfo = {
    prefixName: '哈利',
    suffixName: '波特',
    age: 18,
  }

  // mutation
  @Mutation
  public SET_USER_AGE(age: number) {
    this.userInfo.age = age
  }

  // action
  @Action
  public async getUserAge() {
    const age = await getAge()
    this.SET_USER_AGE(age)
  }
}

export const UserModule = getModule(User)
  • Action类似于Mutation,但是在Action中不能直接修改状态State,只能调用Mutation来修改状态。
  • Action可以包含异步操作
  • JS中的Action第一个参数为store的上下文 context 对象。对象中包含 commit、state 等。
  • TS中的Action没有这个参数,读取State、调用Mutation等时直接使用 this 访问。

    完整模块示例 - user.ts (module)

    ```typescript import { Module, VuexModule, Mutation, Action, getModule } from ‘vuex-module-decorators’ // 获取根store import store from ‘@/store’

function getAge(): Promise { return new Promise(resolve => { setTimeout(() => { resolve(30) }, 2000) }) }

/**

  • @Module 装饰器,表明当前的store是一个模块,同时作为函数传入该模块所需的参数
  • @param ModuleOptions 模块store的配置
  • @param ModuleOptions.name 当前模块所处的命名空间,注意不要与其他模块的命名产生冲突
  • @param ModuleOptions.namespaced 是否开启命名空间,选择是
  • @param ModuleOptions.dynamic 是否为动态注册模块,选择是
  • @param ModuleOptions.store 当前模块挂载的store,直接传入根节点 */ @Module({ name: ‘user’, namespaced: true, dynamic: true, store }) class User extends VuexModule { // state public userInfo = { prefixName: ‘哈利’, suffixName: ‘波特’, age: 18, }

    // getters public get allName() { return ${this.userInfo.prefixName} · ${this.userInfo.suffixName} }

    // mutation @Mutation public SET_USER_AGE(age: number) { this.userInfo.age = age }

    // action @Action public async getUserAge() { const age = await getAge() this.SET_USER_AGE(age) } }

// 使用getModule方法导出 export const UserModule = getModule(User)


- 首先关注 `@Module` 装饰器,下方直接紧跟一个class,继承于 `VuexModule` 类。函数内部传入模块store的配置对象。
- **要特别注意最后一行**,导出时不要直接导出模块的类,要使用从该库中引入的 `getModule` 方法。使用该方法,传入当前模块的类。然后导出函数的返回值。**没有这一步的话,外部的组件无法直接的获取到该store中的各项数据和方法。
<a name="x5Ts0"></a>
## 规范 & 风格指南

- **统一使用模块化和模块动态注册**,即@Module装饰器的参数如上述代码所示。
- State、Getters、Mutation、Action**声明时前面都加上 `public` 关键字**。表明外部可以调用。
- class内部的代码**按照State、Getters、Mutation、Action这个顺序进行排序**。
- **State、Getters、Action命名**都按照**小驼峰**格式命名,语义化命名。
- **Mutation**按照常量命名,**全部大写,单词之间使用下划线 `_` 分隔**,与js的Mutation命名规范保持一致。
- 模块的 `name` 使用小驼峰,文件名与模块name相同,class声明使用大驼峰,导出的模块使用class的命名后面加上 `Module` 以此与class区分。如用户模块,则如上述代码所示:`name: 'user'`、`class User`、`export const UserModule = getModule(User)`,文件名为 `user.ts`

<a name="xQxs8"></a>
# 单文件组件(.vue)内使用
<a name="Vg5Vg"></a>
## 数据层使用Store
```typescript
import { Component, Vue } from 'vue-property-decorator'

// 引入要用到的Store模块
import { UserModule } from '@/store/modules/user'

@Component
export default class UserCard extends Vue {
  private async mounted() {
    // 直接访问、调用即可
    // 访问 State
    console.log(UserModule.userInfo.age) // 18

    // 访问 Getters
    console.log(UserModule.allName) // 哈利 · 波特

    // 使用 Mutation
    UserModule.SET_USER_AGE(20)
    console.log(UserModule.userInfo.age) // 20

    // 使用 Action
    await UserModule.getUserAge()
    console.log(UserModule.userInfo.age) // 30
  }
}
  • 组件内引用,在引入模块后直接调用和使用即可。
  • 需要注意的是,state在这里使用时也是不能直接赋值修改的,只能通过mutation和action来操作state。

    视图层使用Store

    数据层面的vuex使用方法有了,但是众所周知,挂载在页面上时,需要将数据绑定在vue实例(即组件内的this上)上。模板才能读取到数据。那么数据要如何绑定到组件实例上呢? ```vue

直接将整个模块挂载至class内即可。前面加上public readonly表明是只读属性。名字使用相同的即可。在class内一个通过名字直接访问,一个通过 `this.xxx` 访问。
<a name="hl86U"></a>
## 完整组件示例 .vue
```vue
<template>
  <div>
    <p>{{ UserModule.allName }}</p>
    <p>{{ UserModule.userInfo.age }}</p>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { UserModule } from '@/store/modules/user'

@Component
export default class Todo extends Vue {
  public readonly UserModule = UserModule

  private async mounted() {
    // 访问 State
    console.log(UserModule.userInfo.age) // 18

    // 访问 Getters
    console.log(UserModule.allName) // 哈利 · 波特

    // 使用 Mutation
    UserModule.SET_USER_AGE(20)
    console.log(UserModule.userInfo.age) // 20

    // 使用 Action
    await UserModule.getUserAge()
    console.log(UserModule.userInfo.age) // 30
  }
}
</script>

规范 & 风格指南

  • 挂载到视图层时,直接挂载到data上,加上public、readonly修饰符,名字相同即可
  • 数据层访问和调用时直接通过引入的模块访问和调用即可
  • 不能直接赋值、修改State数据,必须通过Mutation和Action来操作。