Vue2.5+ Typescript introduces a comprehensive guide to Typescript – the Vuex chapter

Series catalog:

  • Vue2.5+ Typescript introduces a comprehensive guide
  • Vue2.5+ Typescript introduces a comprehensive guide to Typescript – the Vuex chapter

preface

Vuex is at the heart of my decision to introduce Typescript. The code associated with Vuex is littered with this:

Vuex’s dispatch/commit is not a direct reference to the code, but a string type of type. Therefore, if you want to check the specific content of the payload, you can only check the code manually. It’s unbearable.

With typescript interface, at least this can be simplified:

It’s kind of a hassle to write this, but don’t you have to remember Interface types like PostLoginParams? It’s easy. We’ll create an auxiliary function:

You can use Ctrl + space in the editor, and you can get all the parameters in the payload. You don’t have to read the code again and again.

Summary of the status quo

As of November 2017, Vuex support for Typescript is still very weak, with the official library adding.d.ts declarations and no built-in support like vue 2.5.

Vuex-typescript, vuex-ts-decorators, vuex-Typex, vuex-Class, etc. My personal summary is that most of them, with the exception of Vuex-class, are too intrusive to be referential friendly. Vuex-class offers a thin layer of functionality that doesn’t address the core pain points. So, there’s a lot to add manually.

Use this.$store.dispatch/this. codestore.mit/this.$store.state/ this.$store.getters will be lost with each call to this.

Dispatch /commit can be easily bypassed by creating helper functions. There is no good way to manually specify state/getters. If you feel troublesome, you can refer to all of them as any, etc. See this issue for official updates

Hands-on transformation Step 1: Move the code from the shopping-Cart example

The following example is based on the most complex shopping- Cart in vuex’s official examples. See VUe-vuex-typescript-demo for the complete modified code

Preparations:

  • shopping-cartCopy the code to the project directory
  • .jsAll files are renamed to.ts.
  • currency.js/api/shop.js/components/App.vuePeripheral files such as TS transformation
  • npm i -D vuexAdd the dependent

The detailed steps are omitted here and refer to the code base

The second step: State transformation

The state variable is not only required in the store directory (action/getter/mutation), but also in the.vue file (mapState).

  • Each submodule under Store/Modules maintains its own Interface declaration named State
  • In store/index.ts, maintain a total State declaration by summarizing the submodules

Example files in 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} const getters = {added: [], checkoutStatus: null} State) => state.checkoutStatus }Copy the code

The total State of a store/index.ts file is as follows:

// ./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
}
Copy the code

Examples of total State references:

// ./src/store/getters.ts import { State } from './index' const cartProducts: Getter<State, any> = (state: State) => {return state.cart.added. Map (shape => {shape => { })}Copy the code

Thus, type inference can be enabled wherever state is directly referenced

Mutation of hands-on modification

Mutation corresponds to the store.com MIT command.

const mutations = { [types.ADD_TO_CART] (state, { id }) { // ... }}Copy the code

{id} is processed in the preceding step, and the payload parameter is payload.

My set of personal practices:

  • store/modulesUnder the submodule file, for their ownmutationsMaintains the payload Interface declaration
  • Submodules share payload (multiple modules respond to the same commit, etc.)store/index.tsUnified maintenance
  • The new filestore/dispatches.tsFile, with arguments for each direct callcommitMaintain helper functions to apply type derivation

Submodule Payload Example:

// ./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) // ... Open actions = {getAllProducts (context: ActionContextBasic) {shop.getProducts((products: Product[]) => { const payload: ProductsPayload = { products } context.commit(types.RECEIVE_PRODUCTS, payload) }) } }Copy the code

Store /index.ts File Payload

// ./src/store/index.ts

export interface AddToCartPayload {
  id: number
}Copy the code

Store/dispatchs. ts file, commit helper function, see the same file dispatch helper function next step

Take Action to transform

Action corresponds to the store.dispatch command.

const actions = { checkout ({ commit, state }, products) { // ... }}Copy the code

The second parameter, products, payload, is used as the payload of the previous Mutation.

The first argument {commit, state}, the context argument, vuex’s d.ts provides typed ActionContext, which can be used as follows:

import { ActionContext } from 'vuex' const actions = { checkout (context: ActionContext<State, any>, products: CartProduct[]) { context.commit(types.CHECKOUT_REQUEST) // ... }}Copy the code

ActionContext

Pass in two parameters that most actions don’t use to get the required dispatch and commit, which, in my opinion, is extremely difficult to use.
,>

Personally, I prefer the following:

const actions = { checkout (context: { commit: Commit, state: State }, products: CartProduct[]) { context.commit(types.CHECKOUT_REQUEST) // ... }}Copy the code

Action Payload Modification refer to step Mutation.

Store/dispatchs. ts file, dispatch helper function:

// ./src/store/dispatches.ts

import store, { CartProduct, Product } from './index'

export const dispatchCheckout = (products: CartProduct[]) => {
  return store.dispatch('checkout', products)
}Copy the code

Example for calling.vue files:

// ./src/components/Cart.vue import { dispatchCheckout } from '.. /store/dispatches' export default Vue.extend({ methods: { checkout (products: CartProduct[]) {// this.$store. $store. Dispatch ('checkout', products) dispatchCheckout(products)}}})Copy the code

Getters for hands-on transformation

Getter:

const getters = {
  checkoutStatus: state => state.checkoutStatus
}Copy the code

Add state to the declaration:

const getters = {
  checkoutStatus: (state: State) => state.checkoutStatus
}Copy the code

To transform the independent Mutations/Actions/Getters files

Standard writing for independent documents:

// ./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
    }
  })
}Copy the code

Reference:

// ./src/store/index.js

import * as getters from './getters'
export default new Vuex.Store({
  getters
})Copy the code

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 getterTreeCopy the code

Actions/Mutations file transformation is the same as above, and the type is changed to ActionTree, Action, MutationTree, Mutation

Reference:

// ./src/store/index.js

import getters from './getters'
export default new Vuex.Store({
  getters
})Copy the code

The new vuex.Store parameter type StoreOptions is as follows:

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; }Copy the code

So, independent Gettes/Actions/Mutations files, export must be GetterTree/ActionTree MutationTree type

Make a. Vue file call

  • The traditional way of writing all compatible, justmapStateAdd type (state: state) => state. Balabal etc. It’s just of typeany
  • Not recommendedmapState / mapGetters / mapActions / mapMutationsTo explicitly specify the type
  • dispatchcommitThe call can be made through the abovestore/dispatches.tsUnder the auxiliary function, manually open type derivation
  • stategettersType derivation, which can only be specified manually for now. Automatic derivation, estimation will have to wait for official built-in support.

Full call example:

// ./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') } })Copy the code

Vue-class-component + vuex-class

Vue -class- Component + vuex-class

  • vue-class-componentVue official maintenance, low learning cost
  • vuex-class, the authorktsn, vuex and VuE-class-component are the second most active developers in terms of contribution, and the quality is guaranteed

After these two dependencies are introduced, you must add the configuration in tsconfig.json:

{"compilerOptions": {// to enable vue-class-Component and vuex-class you need to enable the option "experimentalDecorators": "StrictFunctionTypes ": false}}Copy the code

Component example:

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) } }Copy the code

conclusion

Writing vuex code in typescript is a bit cumbersome and not complete, but it’s better than nothing. Even if you use any at all, you can take some of the pain out of code shuffling with the help of intelligent hints.

As for further perfect support, wait for the official update.

The complete code

See Github library: vue-vuex-typescript-demo