系列目錄:vue
Vuex 正是我下決心引入Typescript的核心痛點。與 vuex 相關的代碼中,處處充斥着此般寫法:git
再加上vuex的 dispatch/commit
並不是直接引用代碼,而是是經過一個string類型的 type
來標記,如此一來上圖中寫法,若是想查看 payload
的具體內容,甚至不能借助於編輯器的查找定義,只能手動去切代碼查看!簡直苦不堪言。github
而藉助於 typescript
的 interface
接口,至少能夠簡化成以下效果:vuex
這麼寫同樣麻煩,不是還須要記相似PostLoginParams的Interface類型?這簡單,建個輔助函數就是:typescript
編輯器裏一個 Ctrl + 空格
,payload
裏有哪些參數就全出來,不再用去一遍遍翻代碼,效率直線提高!npm
截至當前2017年11月,Vuex對Typescript的支持,仍十分薄弱,官方庫只是添加了一些.d.ts
聲明文件,並無像vue 2.5
這樣內置支持。json
第三方衍生庫 vuex-typescript
, vuex-ts-decorators
, vuex-typex
, vuex-class
等等,我我的的總結,除了vuex-class
外,基本都存在侵入性太強的問題,引用不算友好。而vuex-class
提供的功能其實也是薄薄一層,並不能解決核心痛點。所以,須要手動添加輔助的地方,其實頗多。segmentfault
核心痛點:每次調用 this.$store.dispatch
/ this.$store.commit
/ this.$store.state
/ this.$store.getters
都會伴隨着類型丟失。api
其中,dispatch/commit
能夠經過創建輔助函數形式,簡單繞開。 state/getters
沒有太好辦法,只能手動指定,若以爲麻煩,能夠全都指成 any
,等官方支持。官方動態見此 issue編輯器
如下示例基於
vuex
官方examples
中最複雜的一個 shopping-cart,
改造後的完整代碼見 vue-vuex-typescript-demo
準備工做:
shopping-cart
代碼複製至項目目錄下.js
文件統一重命名爲.ts
,currency.js
/api/shop.js
/components/App.vue
等外圍文件的ts改造npm i -D vuex
添加依賴詳細步驟這裏略去,參照 代碼庫 便可
用到state變量的地方實在太多,不只store
目錄下 action/getter/mutation 均有可能須要,甚至在 .vue
文件裏,mapState
也有引用,所以我我的總結的一套實踐:
store/modules
下文件舉例:
// ./src/store/modules/cart.ts interface Shape { id: number quantity: number } export interface State { added: Shape[] checkoutStatus: 'successful' | 'failed' | null } // initial state // shape: [{ id, quantity }] const state: State = { added: [], checkoutStatus: null } // 需引用state的地方舉例: const getters = { checkoutStatus: (state: State) => state.checkoutStatus }
store/index.ts
文件總 State 舉例:
// ./src/store/index.ts import { State as CardState } from './modules/cart' import { State as ProductsState } from './modules/products' export interface State { cart: CardState, products: ProductsState }
總 State
引用示例:
// ./src/store/getters.ts import { State } from './index' const cartProducts: Getter<State, any> = (state: State) => { return state.cart.added.map(shape => { // 此處shape自動推導出Shape類型 // ... 詳見源碼 }) }
如此,全部直接引用 state
的地方,都可啓用類型推導
Mutation
對應 store.commit
命令,常見寫法:
const mutations = { [types.ADD_TO_CART] (state, { id }) { // ... } }
state 上步已處理{ id }
,payload
參數,即爲開篇介紹類型缺失的重災區。
個人一套我的實踐:
store/modules
下的子模塊文件,爲本身的mutations
維護 payload Interface聲明store/index.ts
中統一維護store/dispatches.ts
文件,爲每個直接調用的帶參commit
維護輔助函數,以應用類型推導子模塊 payload
聲明舉例:
// ./src/store/modules/products.ts import { Product, AddToCartPayload } from '../index' export interface ProductsPayload { products: Product[] } const mutations = { [types.RECEIVE_PRODUCTS] (state: State, payload: ProductsPayload) { state.all = payload.products }, [types.ADD_TO_CART] (state: State, payload: AddToCartPayload) { const product = state.all.find(p => p.id === payload.id) // ... } } // mutations調用舉例: const actions = { getAllProducts (context: ActionContextBasic) { shop.getProducts((products: Product[]) => { const payload: ProductsPayload = { products } context.commit(types.RECEIVE_PRODUCTS, payload) }) } }
store/index.ts
文件公共 payload
聲明舉例:
// ./src/store/index.ts export interface AddToCartPayload { id: number }
store/dispatches.ts
文件,commit
輔助函數,參見下步同文件dispatch
輔助函數
Action
對應 store.dispatch
命令,常見寫法:
const actions = { checkout ({ commit, state }, products) { // ... } }
其中第二個參數products
,payload
參數,用法同上步 Mutation
的 payload
參數,再也不贅述。
第一個參數{ commit, state }
,context
參數,vuex
的 d.ts
提供有類型 ActionContext
,用法以下:
import { ActionContext } from 'vuex' const actions = { checkout (context: ActionContext<State, any>, products: CartProduct[]) { context.commit(types.CHECKOUT_REQUEST) // ... } }
ActionContext<State, RootState>
傳入兩個大部分Action根本用不到的參數,才能獲得須要的dispatch
, commit
,在我看來,難用至極。
我的更喜歡以下寫法:
const actions = { checkout (context: { commit: Commit, state: State }, products: CartProduct[]) { context.commit(types.CHECKOUT_REQUEST) // ... } }
Action
payload 改造參見步驟 Mutation
,再也不贅述。
store/dispatches.ts
文件,dispatch
輔助函數:
// ./src/store/dispatches.ts import store, { CartProduct, Product } from './index' export const dispatchCheckout = (products: CartProduct[]) => { return store.dispatch('checkout', products) }
.vue
文件調用舉例:
// ./src/components/Cart.vue import { dispatchCheckout } from '../store/dispatches' export default Vue.extend({ methods: { checkout (products: CartProduct[]) { // this.$store.dispatch 寫法可用,但不帶類型推導 // this.$store.dispatch('checkout', products) dispatchCheckout(products) // 帶有類型智能提示 } } })
Getter
常見寫法:
const getters = { checkoutStatus: state => state.checkoutStatus }
須要改的很少,state 加上聲明便可:
const getters = { checkoutStatus: (state: State) => state.checkoutStatus }
獨立文件常規寫法:
// ./src/store/getters.js export const cartProducts = state => { return state.cart.added.map(({ id, quantity }) => { const product = state.products.all.find(p => p.id === id) return { title: product.title, price: product.price, quantity } }) }
引用:
// ./src/store/index.js import * as getters from './getters' export default new Vuex.Store({ getters })
typescript
下均需改造:
// ./src/ import { GetterTree, Getter } from 'vuex' import { State } from './index' const cartProducts: Getter<State, any> = (state: State) => { return state.cart.added.map(shape => { // ... }) } const getterTree: GetterTree<State, any> = { cartProducts } export default getterTree
Actions/Mutations 文件改造同上,類型換成 ActionTree, Action, MutationTree, Mutation便可
引用:
// ./src/store/index.js import getters from './getters' export default new Vuex.Store({ getters })
緣由是vuex
定義,new Vuex.Store
參數類型 StoreOptions 以下:
export interface StoreOptions<S> { state?: S; getters?: GetterTree<S, S>; actions?: ActionTree<S, S>; mutations?: MutationTree<S>; modules?: ModuleTree<S>; plugins?: Plugin<S>[]; strict?: boolean; }
因而,獨立Gettes/Actions/Mutations文件,export 必須是GetterTree/ActionTree/MutationTree類型
mapState
爲state添加類型 (state: State) => state.balabal 等不多改動便可正常運行。只是類型均爲 any
mapState
/ mapGetters
/ mapActions
/ mapMutations
,以明確指定類型dispatch
及 commit
調用可經過上述 store/dispatches.ts
下輔助函數,手動開啓類型推導state
及 getters
類型推導,暫時只能手動指定。自動推導,估計得等官方內置支持了。完整調用示例:
// ./src/components/ProductList.vue import Vue from 'vue' // import { mapGetters, mapActions } from 'vuex' import { Product } from '../store' import { dispatchAddToCart } from '../store/dispatches' export default Vue.extend({ computed: { // ...mapGetters({ // products: 'allProducts' // }) products (): Product[] { return this.$store.getters.allProducts } }, methods: { // ...mapActions([ // 'addToCart' // ]) addToCart (p: Product) { dispatchAddToCart(p) } }, created () { this.$store.dispatch('getAllProducts') } })
若是以爲以上廢棄 mapState
/ mapGetters
後的寫法繁瑣,可引入vue-class-component
+ vuex-class
,開啓組件式寫法
vue-class-component
,vue官方維護,學習成本低vuex-class
,做者 ktsn
,vuex及vue-class-component貢獻排第二(第一尤雨溪了)的活躍開發者,質量仍是有保障的引入這倆依賴後,須在 tsconfig.json
添加配置:
{ "compilerOptions": { // 啓用 vue-class-component 及 vuex-class 須要開啓此選項 "experimentalDecorators": true, // 啓用 vuex-class 須要開啓此選項 "strictFunctionTypes": false } }
Component
寫法示例:
import Vue from 'vue' import { Product } from '../store' // import { dispatchAddToCart } from '../store/dispatches' import Component from 'vue-class-component' import { Getter, Action } from 'vuex-class' @Component export default class Cart extends Vue { @Getter('cartProducts') products: CartProduct[] @Getter('checkoutStatus') checkoutStatus: CheckoutStatus @Action('checkout') actionCheckout: Function get total (): number { return this.products.reduce((total, p) => { return total + p.price * p.quantity }, 0) } checkout (products: CartProduct[]) { // dispatchCheckout(products) this.actionCheckout(products) } }
在現階段 vuex
官方未改進 typescript
支持下,用 typescript 寫 vuex 代碼,的確有些繁瑣,並且支持也稱不上全面,不過,總比沒有強。哪怕都用 any
,也能借助智能提示減輕一些代碼翻來翻去的痛苦。
至於再進一步更完美的支持,等官方更新吧。
見 Github 庫:vue-vuex-typescript-demo