Vue & TypeScript 初體驗 - 使用Vuex (vuex-module-decorators)

上一篇: Vue & TypeScript 初體驗javascript

在上一篇文章中簡單介紹了Vue項目中使用TypeScript, 以及相比原來Vue組件寫法的一些改變.
本文主要針對在TypeScript下Vuex寫法如何更貼近TS的面向對象編程.html

選項

簡單查了下, 目前有2個庫可用於Vue typescriptvue

vuex-class 最近一次更新在8個月前, 且更新頻率不高, 故此選擇了vuex-module-decorators用以學習&驗證java

安裝 vuex-module-decorators

官方文檔: championswimmer.in/vuex-module…ios

npm install -D vuex-module-decorators
複製代碼

orgit

yarn install vuex-module-decorators
複製代碼

Babel 6/7

在@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
複製代碼

TypeScript(3.x)配置(tsconfig.json)

在tsconfig.json中須要設置:typescript

experimentalDecorators: true,
importHelpers: true
複製代碼

在TypeScript 2中須要設置emitHelpers: truenpm

配置

在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()
複製代碼

開始使用

1. 定義一個module

定義一個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'

2. 在store中使用

在你的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()

3. 訪問state

直接經過Store訪問

Import The store
import store from '~/store'

store.state.myMod.someField
複製代碼

在組件中使用this.$store訪問

this.$store.state.myMod.someField
複製代碼

4. 經過getModule()安全訪問Store

除此以外,對於更多類型安全的訪問,咱們可使用getModule(), 修改以前編寫的文件:

  • src/store/index.ts
  • src/store/MyModule.ts
  • src/views/useVuex.vue
// 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>
複製代碼

核心

1. state

類中全部定義的屬性都會被定義爲state屬性, 例如:

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

@Module
export default class Vehicle extends VuexModule {
  wheels = 2
}
複製代碼

等價於:

export default {
  state: {
    wheels: 2
  }
}
複製代碼

2. Getters

全部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
  }
}
複製代碼

3. Mutations

全部被@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函數, 也不能使用箭頭函數來定義, 由於在代碼須要在運行從新綁定執行的上下文.

4. Actions

全部被@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)

警告

  • 若是須要在action函數中運行耗時很長的任務/函數, 建議將該任務定義爲異步函數. 即便你沒有這麼作, 該庫會將你的函數用Promise包裝它並等待它的執行結束.
  • 若是你堅持要使用同步函數執行任務, 請改使用Mutation
  • 千萬不要使用(=>)箭頭函數來定義action函數, 由於在運行時須要動態綁定this上下文.

高級用法

1. 名字空間

閱讀本節前, 須要先了解什麼是vuex名字空間

若是你想經過名字空間的形式來使用module, 需在@Module裝飾器中添加額外的參數. 例如, 如下示例代碼中添加一個namespacedmm的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'),而且咱們須要在裝飾器中使用正確的名稱才能使其正常工做

2. 動態module

閱讀本節前, 須要先了解vuex - dynamic-module-registration

建立一個動態module很簡單, 只須要在 @Module裝飾器中多傳入幾個參數, 但前提條件是, 須要先建立一個Store對象, 並將其傳入到module中.

1) 建立Store

// @/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  /* 若全部的module都是動態建立的, 那這裏的參數爲空 */
})
複製代碼

2) 建立一個動態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才能註冊成功.

相關連接

相關文章
相關標籤/搜索