傳統 vuex 編碼讓人以爲麻煩的一點就是 state、getters、mutation、dispatch 在調用時沒法得到編輯器的智能提示,必須切換文件去查找。本覺得用上 typescript 後這個問題能夠獲得解決,卻發現vuex官方提供的types並無那麼強大...vue
在找尋了一下子各類解決方案後,以爲都存在這樣或那樣的問題(類型須要重複定義、侵入嚴重,和本來寫法徹底不同),因此便本身寫了這麼一個解決方案,在得到了typescript的智能提示支持下卻不須要重複寫各類type和interface,和vuex本來寫法保持一致,對業務代碼侵入極小。node
demo 項目由 vue-cli 3 生成,IDE 爲 VSCODEgit
state 會顯示全部的 module、裏面的屬性及屬性的類型github
/src/store/modules/auth.tsvuex
const moduleState = {
token: '',
tokenExpire: 0,
}
複製代碼
getter 能夠顯示值類型vue-cli
/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);
},
};
複製代碼
commit 會顯示全部註冊的 mutation 以及對應 payload 的類型, 若是 payload 類型不對還會報錯shell
/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];
},
};
複製代碼
和 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);
},
};
複製代碼
上面能夠看到,這個方案在保證了 vuex 原來的寫法(稍微有一點點變化,從 this.$store 換爲 this.$$store)上支持了 typescript 的類型檢查,爲了實現它,vuex 的 store 在初始化的時候咱們須要作一些額外的工做,可是也僅限於這一點額外的工做了,後續的 module(業務)的增長,也徹底像 vuex 原本的寫法那樣去定義各類 state、getters、mutation、action,一勞永逸的得到 typescript 對 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,
};
複製代碼
/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;
}
}
複製代碼
ps: 這個方案可能還不夠好(rootGetters 類型沒有實現等...),歡迎各位交流學習
ppps: 下一步想將這些 ts 類型的代碼抽離成一個工具庫,可是初步想了一下有點難度,就先以 demo 的形式分享一下
地址:github
代碼拉下來之後
yarn install
複製代碼
以後就能夠在 .vue 文件和 .ts 文件中體驗 vuex 得到智能提示的便利了~
若是對你有幫助的話,麻煩給個star唄~