Correctly Typing Vuex with TypeScript

参考文档: https://www.codeproject.com/Tips/5295301/Correctly-Typing-Vuex-with-TypeScript

A walkthrough of statically typing Vuex with TypeScript

TypeScript is a great alternative of JavaScript, and works really well with frontend frameworks like Vue.js. However in the beginning, it can be hard to get the static types right. Sadly, it is not written in the official documentation how to do this. Luckily, with a little experimentation, I managed to do it.

Our Example Vuex Store

In this tip, I will create a Vuex store for storing apples and oranges. Apples and oranges will have their own separate module, and the root store will aggregate these two.

The Apple Module

Let’s look at the first implementation of the Apple module at @/store/AppleModule.ts

import { Apple } from "@/models/Apple";

export interface ApplesState {
    items: Array<Apple>
}

export default {
    namespaced: true,
    state: (): ApplesState => ({
      items: Array<Apple>()
    }),
    mutations: {
        setApples: function(state: ApplesState, apples: Array<Apple>): void {
          state.items = apples;
        }
    }
}

This module has a single property in its state, an array of apples, and a single mutation, that sets this array. The type of the state is declared by the ApplesState interface, which is used as the state parameters type in the mutation.

Let’s include this module in the root store at @/store/intex.ts:

import Vue from 'vue'
import Vuex from 'vuex'
import appleModule, { ApplesState } from './AppleModule'

Vue.use(Vuex)
export interface State {
  apples: ApplesState;
}

export default new Vuex.Store<State>({
  modules: {
    apples: appleModule
  }
})

We declare the type of the root state with the State interface. We use the State as type parameter on the Vuex.Store instance.

Actions and Context

To define actions in the store module, we need to have a Context type. For this, we will need to import the root state type into our apple module, and use it to define the Context type.

import { ActionContext } from "vuex";
import { State } from '.';

type Context = ActionContext<ApplesState, State>;

You can now use this Context type for the context parameter in the action definitions:

loadApples: async function(context: Context): Promise<Array<Apple>> {
  const apples = (await axios.get('apples')).data;
  context.commit('setApples', apples);
  return context.state.items;
}

Orange Module

The orange module is implemented similarly to apples. They are not like apples and oranges after all …

import Vue from 'vue'
import Vuex from 'vuex'
import appleModule, { ApplesState } from './AppleModule'
import orangeModule, { OrangesState } from './OrangeModule'

Vue.use(Vuex)

export interface State {
  apples: ApplesState;
  oranges: OrangesState;
}

export default new Vuex.Store<State>({
  modules: {
    apples: appleModule,
    oranges: orangeModule
  }
})

state 示例

src/models/IconMeta.ts


interface Author {
  name: string
}

interface License {
  title: string
}

export interface IconMeta {
  name: string,
  total: number,
  author: Author,
  license: License,
  icons: string[]
}

src/store/index.ts

import { createStore } from 'vuex'
import { IconMeta } from '@/models/IconMeta'

export interface MetaState {
  iconMetas: IconMeta[]
}

export default createStore({
  state: ():MetaState =>({
    iconMetas: []
  }),
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

赋值: src/services/api.ts

import axios from "axios";
import store from '@/store';

import { IconMeta } from "@/models/IconMeta";

interface IconMetaResp {
  status: number,
  data: IconMeta[]
}

const GetIconMeta = async():Promise<IconMeta[]> =>  {
  let metas: IconMeta[] = []
  if(store.state.iconMetas.length === 0) {
    const url = `${process.env.VUE_APP_SERVER_URL}/api/wl-icons/meta/list`
    try{
      const{data, status}  = await axios.get<IconMetaResp>(url)

      console.log(data, status)
      store.state.iconMetas = data.data
    }catch(e) {
      console.log(e)
    }
  }
  metas = store.state.iconMetas
  
  return metas
}

const MetaApi = {
  GetIconMeta,
}

export default MetaApi