上一篇: Vue & TypeScript 初體驗html
在上一篇文章中簡單介紹了Vue項目中使用TypeScript, 以及相比原來Vue組件寫法的一些改變.
本文主要針對在TypeScript下Vuex寫法如何更貼近TS的面向對象編程.vue
簡單查了下, 目前有2個庫可用於Vue typescriptios
vuex-class 最近一次更新在8個月前, 且更新頻率不高, 故此選擇了vuex-module-decorators用以學習&驗證git
官方文檔: championswimmer.in/vuex-module…github
npm install -D vuex-module-decorators
複製代碼
orvuex
yarn install vuex-module-decorators
複製代碼
在@vue/cli 3.0建立的項目中, @vue/babel-preset-app已經包含了此項Babel項插件.typescript
npm install babel-plugin-transform-decorators --dev
複製代碼
ornpm
yarn install babel-plugin-transform-decorators --dev
複製代碼
在tsconfig.json中須要設置:編程
experimentalDecorators: true, importHelpers: true 複製代碼
在TypeScript 2中須要設置
emitHelpers: true
json
在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才能註冊成功.