vuex對ts的支持太弱?想要得到vuex的智能提示?一個讓 vuex 更好的支持 typescript 的解決方案

傳統 vuex 編碼讓人以爲麻煩的一點就是 state、getters、mutation、dispatch 在調用時沒法得到編輯器的智能提示,必須切換文件去查找。本覺得用上 typescript 後這個問題能夠獲得解決,卻發現vuex官方提供的types並無那麼強大...vue

在找尋了一下子各類解決方案後,以爲都存在這樣或那樣的問題(類型須要重複定義、侵入嚴重,和本來寫法徹底不同),因此便本身寫了這麼一個解決方案,在得到了typescript的智能提示支持下卻不須要重複寫各類type和interface,和vuex本來寫法保持一致,對業務代碼侵入極小。node

demo 項目由 vue-cli 3 生成,IDE 爲 VSCODEgit

效果展現

1. state

state 會顯示全部的 module、裏面的屬性及屬性的類型github

/src/store/modules/auth.tsvuex

const moduleState = {
  token: '',
  tokenExpire: 0,
}
複製代碼

2. getters

getter 能夠顯示值類型vue-cli

avatar

/src/store/modules/auth.tstypescript

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

3. commit

avatar

avatar

commit 會顯示全部註冊的 mutation 以及對應 payload 的類型, 若是 payload 類型不對還會報錯shell

avatar

/src/store/modules/auth.tsapp

const mutations = {
  setToken(state: State, payload: State['token']) {
    state.token = payload || '';
  },
  setTokenExpire(state: State, payload: State['tokenExpire']) {
    state.tokenExpire = payload || 0;
  },
};
複製代碼

/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 || {};
  },
};
複製代碼

/src/store/modules/pageCache.ts

const mutations = {
  setPageToCache(state: State, payload: any) {
    state.pagesName.unshift(payload.pageName);
    setTimeout(() => {
      if (payload.callback) payload.callback();
    }, 0);
  },
  resetPageCache(state: State) {
    state.pagesName = [...cachePages];
  },
};
複製代碼

4. dispatch

和 commit 相似

avatar

avatar

/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);
  },
};
複製代碼

實現方式

上面能夠看到,這個方案在保證了 vuex 原來的寫法(稍微有一點點變化,從 this.$store 換爲 this.$$store)上支持了 typescript 的類型檢查,爲了實現它,vuex 的 store 在初始化的時候咱們須要作一些額外的工做,可是也僅限於這一點額外的工做了,後續的 module(業務)的增長,也徹底像 vuex 原本的寫法那樣去定義各類 state、getters、mutation、action,一勞永逸的得到 typescript 對 vuex 各類調用的支持。

1. 改造 vuex 部分

先來看看最基本的 vuex 代碼

/store/modules/auth.ts

// ts 校驗會提示好幾個函數具備隱式 any 入參,暫略過
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,
};
複製代碼

/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;
複製代碼

下面咱們給它加一點 Magic

/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);

// 從 module 的 state 中提取 state 的類型並集合
interface State {
  auth: typeof auth.state;
  user: typeof user.state;
  pageCache: typeof pageCache.state;
}
// 將 getter 函數轉換成 {getterName: getterFuncsReturnType} 的對象類型
export type ReturnGetters<T extends { [key: string]: (...args: any) => any }> = {
  [P in keyof T]: ReturnType<T[P]>;
}
// 提取全部 module 的 getter 函數類型對象
type GettersFuncs = (
  typeof auth.getters
  & typeof user.getters
  & typeof pageCache.getters
)
// 將 getter 轉換成對象
type Getters = ReturnGetters<GettersFuncs>
// 提取 mutation 函數類型
type CommitFuncs = (
  typeof auth.mutations
  & typeof user.mutations
  & typeof pageCache.mutations
)
// 將 mutation 函數名及 payload 類型轉換成 commit 函數的兩個入參類型
interface Commit {
  <T extends keyof CommitFuncs>(type: T, payload?: Parameters<CommitFuncs[T]>[1]): void;
}
// dispatch 處理步驟同 commit
type DispatchFuncs = (
  typeof auth.actions
  & typeof user.actions
  & typeof pageCache.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;

// 其餘 ts 文件解構導入時得到每一個對象的改造後類型
export const { state } = store;
export const { getters }: { getters: Getters } = store; // 定義 getters 的類型
export const { commit }: { commit: Commit } = store; // 定義 commit 的類型
export const { dispatch }: { dispatch: Dispatch } = store; // 定義 commit 的類型

// 導出類型 Store 以便在 Vue 原型上定義類型
export interface Store {
  state: State;
  getters: Getters;
  commit: Commit;
  dispatch: Dispatch;
}
複製代碼

爲了不循環引用,咱們須要在 /src/types/ 下面建一個 .d.ts 文件 ,來讓各個 module 能夠引用到全局的 State (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']; // 全局的 dispatch, 有 ts 提示支持
  commit: Store['commit']; // 全局的 commit, 有 ts 提示支持
  state: S;
  getters: G;
  rootState: Store['state']; // 全局的 state, 有 ts 提示支持
  rootGetters: any; // 暫時還沒法實現將全局 getter 定義過來,會出現類型循環引用問題
}
複製代碼

最後去修改咱們的 module(僅舉例auth)

/src/store/modules/auth.ts

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

const moduleState = {
  token: '',
  tokenExpire: 0,
};
type State = typeof moduleState; // 提取 state 類型

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>; // 提取 getter 類型

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,
};
複製代碼

2.改造 vue 實例化 vuex 部分

/src/main.ts

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store, { Store } from './store/index'; 
// 其餘 ts 文件在直接解構引入的時候,也能夠得到智能提示
// import { state, getters, commit, dispatch } from './store/index';

Vue.config.productionTip = false;

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

// 將改造事後的 Store 類型聲明到 vue 的原型上,這樣就能夠在.vue 文件中得到 IDE 的智能提示了
// 由於下面去聲明一個新的 Store 類型的時候,沒法覆蓋 vue 原有的 $store 類型聲明,因此採起一個新的名字 $$store 來應用新的類型,本質上都是 app.$store
Vue.prototype.$$store = app.$store;

declare module 'vue/types/vue' {
  interface Vue {
    $$store: Store;
  }
}

複製代碼

至此 vuex 的 typescript 支持改造就完成了(其中 /src/store/index.ts 徹底能夠用nodejs寫個腳本讀取全部module文件名而後用mustache根據模板來生成,每次新增模塊時跑一行命令就更新了),以後就能夠愉快的使用 vuex 本來的寫法又不用來回切文件找各類命名了,而且寫錯的時候 ts 還會校驗錯誤,媽媽不再用擔憂我犯這種低級錯誤了~

ps: 這個方案可能還不夠好(rootGetters 類型沒有實現等...),歡迎各位交流學習

ppps: 下一步想將這些 ts 類型的代碼抽離成一個工具庫,可是初步想了一下有點難度,就先以 demo 的形式分享一下

demo

地址:github

代碼拉下來之後

yarn install
複製代碼

以後就能夠在 .vue 文件和 .ts 文件中體驗 vuex 得到智能提示的便利了~

若是對你有幫助的話,麻煩給個star唄~

  • 做者:BarneyZhao
  • 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索