上一篇: Vue & TypeScript 初体验javascript
在上一篇文章中简单介绍了Vue项目中使用TypeScript, 以及相比原来Vue组件写法的一些改变.
本文主要针对在TypeScript下Vuex写法如何更贴近TS的面向对象编程.html
简单查了下, 目前有2个库可用于Vue typescriptvue
vuex-class 最近一次更新在8个月前, 且更新频率不高, 故此选择了vuex-module-decorators用以学习&验证java
官方文档: championswimmer.in/vuex-module…ios
npm install -D vuex-module-decorators
复制代码
orgit
yarn install vuex-module-decorators
复制代码
在@vue/cli 3.0建立的项目中, @vue/babel-preset-app已经包含了此项Babel项插件.github
npm install babel-plugin-transform-decorators --dev
复制代码
orvuex
yarn install babel-plugin-transform-decorators --dev
复制代码
在tsconfig.json中须要设置:typescript
experimentalDecorators: true,
importHelpers: true
复制代码
在TypeScript 2中须要设置
emitHelpers: true
npm
在vuex-module-decorators@0.9.3版本开始, 代码最终发布为ES5格式, 所以下面的部分主要针对v0.9.2及之前的版本
该包最终生成的代码是ES2015(ES6)格式的, 所以, 若是你的Vue项目若是最终生成的是ES6, 那不须要作什么. 但若是是ES5(为兼容旧浏览器, 如IE9, IE10, IE11),那么在经过@vue/cli建立的工程中, 须要添加如下配置进行转换:
// in your vue.config.js
module.exports = {
/* ... other settings */
transpileDependencies: ['vuex-module-decorators']
}
复制代码
使用该库以后, 写vuex modules能够像下面这样:
// 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')
}
}
复制代码
其等价于:
// 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)
}
}
}
复制代码
想要利用类型安全的好处, 就不能按一般的方式来dispatch / commit..., 如:
store.commit('updatePosts',帖子)
await store.dispatch('fetchPosts')
复制代码
这样写, 不能利用类型安全的特性, 且IDE也不会提供相关的帮助提示等. 最好的方式是使用用getModule
访问器, 来利用更多类型安全机制
新的写法:
import { getModule } from 'vuex-module-decorators'
import Posts from `~/store/posts.js`
const postsModule = getModule(Posts)
// 访问posts模块
const posts = postsModule.posts
// 使用getters
const commentCount = postsModule.totalComments
// commit mutation
postsModule.updatePosts(newPostsArray)
// dispatch action
await postsModule.fetchPosts()
复制代码
定义一个modules, 须要建立一个class且extends至VuexModule
, 而且Module
装饰它.
// eg. src/store/MyModule.ts
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class MyModule extends VuexModule {
someField: string = 'somedata'
}
复制代码
vuex
中一样有一个名为Module
的类, 但它不是一个装饰器, 千万不要使用错了.
❌
import {Module} from 'vuex'
✔
️ import {Module} from 'vuex-module-decorators'
在你的Store, 能够将类MyModule
做为一个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
}
})
复制代码
注意: 咱们使用MyModule类的方式与经典的面向对象编程不一样,相似于vue-class-component的工做方式。将类自己用做模块,而不是由类构造的对象. 所以下面的写法是错误的.
new MyModule()❌
Import The store
import store from '~/store'
store.state.myMod.someField
复制代码
this.$store
访问this.$store.state.myMod.someField
复制代码
除此以外,对于更多类型安全的访问,咱们可使用getModule()
, 修改以前编写的文件:
// src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {}
})
复制代码
注意: src/store/index.ts没有定义任何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'
}
复制代码
// 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) // 取得Store中某一module @Component export default class UseVuex extends Vue { hello: string = $myMod.someField } </script>
复制代码
类中全部定义的属性都会被定义为state属性, 例如:
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
}
复制代码
等价于:
export default {
state: {
wheels: 2
}
}
复制代码
全部get 函数, 都会都转化为vuex的getters, 例如:
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
// get axles 将会转化为vuex的getters
get axles() {
return wheels / 2
}
}
复制代码
等价于:
export default {
state: {
wheels: 2
},
getters: {
axles: (state) => state.wheels / 2
}
}
复制代码
全部被@Mutation
装饰器装饰的函数, 都会被转化为vuex mutations. 例如:
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
装饰某一函数后, 函数内的this
上下文即指向当前的state.
所以, 若是想修改state中某些属性的值, 能够由原来的state.item++
直接写为this.item++
Muation函数不可为async函数, 也不能使用箭头函数来定义, 由于在代码须要在运行从新绑定执行的上下文.
全部被@Actions
装饰器装饰的函数, 都会被转化为vuex Actions. 例如:
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)
}
}
复制代码
等价于
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装饰后,在函数内commit时, 只需调用this.context.commit('mutationName',mutPayload)
Mutation
阅读本节前, 须要先了解什么是vuex名字空间
若是你想经过名字空间的形式来使用module, 需在@Module
装饰器中添加额外的参数. 例如, 如下示例代码中添加一个namespaced
为mm
的module
@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
}
})
复制代码
@Module
装饰器的属性字段name值, 必需要new store({modules: {}})中注册module name名称要一致.
手动保持这两个相同会使编码显得不优雅,但这一点很重要。咱们必须将:
this.store.dispatch('action')
调用转换为this.store.dispatch('name / action'),而且咱们须要在装饰器中使用正确的名称才能使其正常工做
阅读本节前, 须要先了解vuex - dynamic-module-registration
建立一个动态module很简单, 只须要在 @Module装饰器中多传入几个参数, 但前提条件是, 须要先建立一个Store对象, 并将其传入到module中.
// @/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
/* 若全部的module都是动态建立的, 那这里的参数为空 */
})
复制代码
// @/store/modules/MyModule.ts
import store from '@/store' // 注意引入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 */
}
复制代码
到目前为止, vuex-module-decorators 暂不支持动态嵌套module
确保你在import/required
时, store实例已经建立, 它必需要在动态module以前已经建立好.
store实例必须提早建立好, 这很重要. 由于store实例将在以参数的形式传递到@Module
装饰器中, 这样动态module才能注册成功.