One of the annoying things about traditional VUEX encoding is that state, getters, mutation, and Dispatch cannot get an intelligent reminder from the editor when called and must switch files to find it. We thought we could fix this with typescript, but vuex’s official types aren’t that powerful…

After looking around for a while and realizing that there are some problems (types are repetitive, intrusive, and completely different from what they were written), I write a solution that doesn’t need to repeat types and interfaces with typescript’s smart hints. Consistent with the original writing of VUex, minimal intrusion into business code.

Demo project is generated by vue-cli 3, IDE is VSCODE

Results show

1. state

State displays all modules, their properties, and their types

/src/store/modules/auth.ts

const moduleState = {
  token: ' '.tokenExpire: 0,}Copy the code

2. getters

A getter displays a value type

/src/store/modules/auth.ts

const moduleGetters = {
  isLogin(state: State, getters: any, rootState: Store['state'].rootGetters: any) {
    return!!!!! state.token && (!! rootState.user.userId || !! rootState.user.accountId); }};Copy the code

3. commit

Commit displays all the mutations registered and the payload type. If the payload type is incorrect, an error is reported

/src/store/modules/auth.ts

const mutations = {
  setToken(state: State, payload: State['token']) {
    state.token = payload || ' ';
  },
  setTokenExpire(state: State, payload: State['tokenExpire']) {
    state.tokenExpire = payload || 0; }};Copy the code

/src/store/modules/user.ts

const mutations = {
  setAccountId(state: State, payload: State['accountId']) {
    state.accountId = payload || ' ';
  },
  setUserId(state: State, payload: State['userId']) {
    state.userId = payload || ' ';
  },
  setUserInfo(state: State, payload: State['info']) { state.info = payload || {}; }};Copy the code

/src/store/modules/pageCache.ts

const mutations = {
  setPageToCache(state: State, payload: any) {
    state.pagesName.unshift(payload.pageName);
    setTimeout((a)= > {
      if (payload.callback) payload.callback();
    }, 0); }, resetPageCache(state: State) { state.pagesName = [...cachePages]; }};Copy the code

4. dispatch

Similar to commit

/src/store/modules/auth.ts

const actions = {
  updateAuthData({ commit }: ActionContext<State, Getters>, payload: any) {
    commit('setToken', payload.token);
    commit('setTokenExpire', payload.expire);
  },
  cleanAuthData({ commit }: ActionContext<State, Getters>) {
    commit('setToken'.' ');
    commit('setTokenExpire'.0); }};Copy the code

implementation

As you can see above, this method keeps the format of vuex (slightly changed from this.$store to this.? Store supports typescript type checking. To implement it, vuex’s store does some extra work at initialization, but that’s all it takes. Define state, getters, mutation, and Action exactly as vuex is written, and get typescript support for vuex calls once and for all.

1. Reconstruct vuEX

Let’s start with the most basic vuex code

/store/modules/auth.ts

// the ts check will indicate that several functions have implicit any input parameters
const moduleState = {
  token: ' '.tokenExpire: 0};const moduleGetters = {
  isLogin(state, getters, rootState, rootGetters) {
    return!!!!! state.token && (!! rootState.user.userId || !! rootState.user.accountId); }};const mutations = {
  setToken(state, payload) {
    state.token = payload || ' ';
  },
  setTokenExpire(state, payload) {
    state.tokenExpire = payload || 0; }};const actions = {
  updateAuthData({ commit }, payload) {
    commit('setToken', payload.token);
    commit('setTokenExpire', payload.expire);
  },
  cleanAuthData({ commit }) {
    commit('setToken'.' ');
    commit('setTokenExpire'.0); }};export default {
  state: moduleState,
  getters: moduleGetters,
  mutations,
  actions,
};
Copy the code

/store/index.ts

import Vue from 'vue';
import Vuex from 'vuex';

import auth from './modules/auth';
import user from './modules/user';
import pageCache from './modules/pageCache';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    auth,
    user,
    pageCache,
  },
});
export default store;
Copy the code

Now let’s add a little bit more to itMagic

/store/index.ts

import Vue from 'vue';
import Vuex from 'vuex';

import auth from './modules/auth';
import user from './modules/user';
import pageCache from './modules/pageCache';

Vue.use(Vuex);

// Extract the state type from module state and set it
interface State {
  auth: typeof auth.state;
  user: typeof user.state;
  pageCache: typeof pageCache.state;
}
// Convert the getter function to the object type {getterName: getterFuncsReturnType}
export type ReturnGetters<T extends { [key: string]: (. args: any) = > any }> = {
  [P in keyof T]: ReturnType<T[P]>;
}
// Extract getter-type objects for all Modules
type GettersFuncs = (
  typeof auth.getters
  & typeof user.getters
  & typeof pageCache.getters
)
// Convert getters to objects
type Getters = ReturnGetters<GettersFuncs>
// Extract the mutation function type
type CommitFuncs = (
  typeof auth.mutations
  & typeof user.mutations
  & typeof pageCache.mutations
)
// Convert the mutation function name and payload type to the two input parameter types of the commit functioninterface Commit { <T extends keyof CommitFuncs>(type: T, payload? : Parameters<CommitFuncs[T]>[1) :void;
}
// Dispatch The procedure is the same as commit
type DispatchFuncs = (
  typeof auth.actions
  & typeof user.actions
  & typeofpageCache.actions ) interface Dispatch { <T extends keyof DispatchFuncs>(type: T, payload? : Parameters<DispatchFuncs[T]>[1) :Promise<any>;
}

const store = new Vuex.Store<State>({
  modules: {
    auth,
    user,
    pageCache,
  },
});
export default store;

// Other TS files deconstruct the import to get the modified type of each object
export const { state } = store;
export const { getters }: { getters: Getters } = store; // Define the type of getters
export const { commit }: { commit: Commit } = store; // Define the commit type
export const { dispatch }: { dispatch: Dispatch } = store; // Define the commit type

Export the type Store to define the type on the Vue prototype
export interface Store {
  state: State;
  getters: Getters;
  commit: Commit;
  dispatch: Dispatch;
}
Copy the code

To avoid circular references, we need to create a.d.ts file under/SRC /types/ to allow modules to reference global states (rootState, commit, dispatch).

/src/types/store.d.ts

import { Store as s } from '.. /store/index';

export { ReturnGetters } from '.. /store/index';
export interface Store extends s {}
export interface ActionContext<S, G> {
  dispatch: Store['dispatch']; // Global dispatch with TS prompt support
  commit: Store['commit']; // Global commit, with TS prompt support
  state: S;
  getters: G;
  rootState: Store['state']; // Global state, with ts prompt support
  rootGetters: any; // It is not yet possible to define the global getter, so there is a type loop reference problem
}
Copy the code

Finally, modify our module (auth for example only)

/src/store/modules/auth.ts

import { ReturnGetters, Store, ActionContext } from '.. /.. /types/store';

const moduleState = {
  token: ' '.tokenExpire: 0}; type State =typeof moduleState; // Extract the state type

const moduleGetters = {
  isLogin(state: State, getters: any, rootState: Store['state'].rootGetters: any) {
    return!!!!! state.token && (!! rootState.user.userId || !! rootState.user.accountId); }}; type Getters = ReturnGetters<typeof moduleGetters>; // Extract the getter type

const mutations = {
  setToken(state: State, payload: State['token']) {
    state.token = payload || ' ';
  },
  setTokenExpire(state: State, payload: State['tokenExpire']) {
    state.tokenExpire = payload || 0; }};const actions = {
  updateAuthData({ commit }: ActionContext<State, Getters>, payload: any) {
    commit('setToken', payload.token);
    commit('setTokenExpire', payload.expire);
  },
  cleanAuthData({ commit }: ActionContext<State, Getters>) {
    commit('setToken'.' ');
    commit('setTokenExpire'.0); }};export default {
  state: moduleState,
  getters: moduleGetters,
  mutations,
  actions,
};
Copy the code

2. Modify vUE to instantiate the VUEX part

/src/main.ts

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store, { Store } from './store/index'; 
// Other TS files can also get smart hints when directly destructed
// import { state, getters, commit, dispatch } from './store/index';

Vue.config.productionTip = false;

const app = new Vue({
  router,
  store,
  render: h= > h(App),
}).$mount('#app');

// Declare the modified Store type to the vue prototype, so that you can get IDE smart hints in the.vue file
$Store; $Store; $Store; $Store; $Store; Store to apply new types, essentially app.$storeVue.prototype.? store = app.$store; declaremodule 'vue/types/vue' {
  interface Vue {
    ? store: Store; }}Copy the code

At this point, vuex’s typescript support is complete (where/SRC /store/index.ts can just write a script with nodejs that reads all module names and then generate it with mustache, which updates each new module with a single line of command). After that, I can happily use the original way of writing vuex without cutting back and forth to find a variety of names, and TS will check the error when I make a mistake, my mother no longer need to worry about me making such a stupid mistake

Ps: This scheme may not be good enough (rootGetters type not implemented etc…) Welcome to exchange and study

PPPS: The next step is to separate these TS types of code into a tool library, but the initial thought is a little difficult, so I will share the demo form first

demo

Address: making

After the code is pulled down

yarn install
Copy the code

You can then experience the convenience of vuex getting smart hints in.vue files and.ts files

If it is helpful to you, please give me a star

  • Author: BarneyZhao
  • Copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.