前言
Vuex v4.0版本以上大幅改善了對 Typescript的支援、以及對 composition-api的整合,但是 Vuex v4.x版只支援 Vue3以上版本。
也就是說如果開發環境是 Vue v2.x版本的話,Vuex也就只能使用 v3.x版本。雖然說 Vuex v3.x版對於 Typescript的支援上也些限制和不順手,但只要做好註記 (annotation)的工作,基本上我覺得型別推導上還算蠻完整的,唯獨 Component中要引入 Vuex的時候就會有些限制,只有 state
可以型別推導,其餘 mutation
/action
等都會是 any
型別。不過對我個人來說有 state
型別推導就已經是相當實用,其他的就還是我可以忍受的範圍了。
Vuex 使用 Typescript寫法
State
要將整個 state
註記成一個 Type或 Interface。如果 Vuex有使用 module功能,則每個子模組的 state
各自維護自己的 Interface/Type。
如下例子是名稱為 cart
的模組的 state
:
// @/store/modules/cart/state.ts
export type CartState = {
added: {
id: number
quantity: number
};
checkoutStatus: 'successful' | 'failed' | null
}
const state: CartState = {
added: [],
checkoutStatus: null
}
export default state
在 Vuex的 state
是一個 tree結構,根節點的 state
稱之為 rootState
,而modules的 state
會是rootState
底下的一個子節點,所以在模組化Vuex的rootState
在註記型別的時候需要把 modules的 state
型別放入。
如下範例:
// @/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
// 子模組的 state
import cart, { CartState } from './modules/cart/state'
// 最外層的 state
import appState, { AppState } from './state'
Vue.use(Vuex)
// 根節點的 state-tree
export type RootState = AppState & {
cart: CartState;
}
export default new Vuex.Store({
modules: {
cart
},
state: appState as RootState,
// 略
})
Action
vuex提供 ActionContext<State, RootState>
提供給context
做註記,泛型中有兩個參數分別是該模組的 state
的型別以及 rootState
的型別,如下:
import { ActionContext } from 'vuex'
import { State } from './state.ts'
import { RootState } from '../state.ts'
const actions = {
checkout (context: ActionContext<State, RootState>, products: CartProduct[]) {
context.commit(types.CHECKOUT_REQUEST)
// ...
}
}
解構的寫法
import { Commit } from 'vuex'
import { State } from './state.ts'
const actions = {
checkout ({ commit: Commit, state: State }, products: CartProduct[]) {
context.commit(types.CHECKOUT_REQUEST)
// ...
}
}
Mutation
註記 payload的型別以及在 state.ts中註記的 state型別
import { AddToCartPayload } from '../index.ts'
import { State } from './state.ts'
const mutations = {
addToCart (state: State, payload: AddToCartPayload) {
const product = state.all.find(p => p.id === payload.id)
// ...
}
}
Getters
各別的 getters函式可使用 Getter<State, RootState>
, getters模組物件需使用GetterTree<State, RootState>
// ./src/
import { GetterTree, Getter } from 'vuex'
import { State } from './index'
import { RootState } from '@/store/index'
const cartProducts: Getter<State, RootState> = (state: State) => {
return state.cart.added.map(shape => {
// ...
})
}
const getterTree: GetterTree<State, RootState> = {
cartProducts
}
export default getterTree
簡潔寫法:直接使用 getterTree把所有 getter函式寫在一起,就不用各別註記 getter函式的型別,這樣會更方便一些
// ./src/
import { GetterTree } from 'vuex'
import { State } from './index'
const getterTree: GetterTree<State, any> = {
cartProducts (state) => {
// state的型別 typescript會自行推斷出來
return state.cart.added.map(shape => {
// ...
})
}
}
export default getterTree
Component中使用 Vuex
首先,關於在 Vue2中 Component寫 Typescript雖然不是本文重點,不過既然提到了還是稍微說一下,這裡是極度強烈建議安裝 composition-api,關於使用方式可參考我之前寫的「Vue2的 Option-Based Component中使用Typescript的簡易方式」。你可以不需改變原本的 Option-Api寫法,就可以完完全全的解決原本在 Vue2中 Typescript中支援不良的問題;當然後既然都安裝 composition-api了,也可以直接使用新的 composition-api來開發。
以下針對幾種常用的引入 Vuex方式來做說明:
1.mapState/mapGetters/mapActions/mapMutations
這個寫法是 Option-Api的寫法。很顯然他推導出來都會是 any
型別,所以這種寫法可以直接放棄了。
2.this.$store
這個寫法是 Option-Api的寫法。完成文章上述所有的型別註記之後,他雖然推導的出一些基本的 Vuex型別,但推導不出 state
的型別,在編輯器中滑鼠移過 this.$store
上會發現,他推導出的型別是 Store<any>
。看來這個可能還需要額外的一些工作才能把型別宣告到全域的 $store
物件上。不過我覺得有點麻煩,就放棄嘗試了。
3.import store
這個是我覺得最合理的使用方式,雖然他也只推導的出 state
,但我覺得這已經是一個很大的幫助了,寫法如下:
import store from '@/store/index'
store.state // 可推斷出型別
store.dispatch('checkout', true) // function參數無法推斷型別
總結來說,使用 import store來引入 Vuex是最合理也簡單的方式,至於 actions
和 mutations
無法推導出型別這個問題,如果要解決的話應該可以嘗試寫 helper function來解決,不過這樣對我來說實在有點太過囉唆,所以我是選擇忽略這個小問題了。
參考來源
Vue2.5+ Typescript 引入全面指南 - Vuex篇