简介
由于官方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。
目录结构
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')
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:
使用ts的写法为: ```typescript import { Module, VuexModule, getModule } from ‘vuex-module-decorators’export default { state: { userInfo: { prefixName: '哈利', suffixName: '波特', age: 18, } }, getters: { allName: (state) => `${state.userInfo.prefixName} · ${state.userInfo.suffixName}` }, }
@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(() => {
}, 2000) }) }resolve(30)
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
/**
- @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{{ UserModule.allName }}
{{ UserModule.userInfo.age }}
直接将整个模块挂载至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来操作。