Skip to main content

Vue2 的 Vuex使用 Typescript寫法

· 7 min read
Adam You

前言

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是最合理也簡單的方式,至於 actionsmutations無法推導出型別這個問題,如果要解決的話應該可以嘗試寫 helper function來解決,不過這樣對我來說實在有點太過囉唆,所以我是選擇忽略這個小問題了。

參考來源

Vue2.5+ Typescript 引入全面指南 - Vuex篇

https://segmentfault.com/a/1190000011864013