
最终实现效果
使用
import { useStates, useGetters, useMutations, useActions } from '@/hooks';
// 获取模块中的state
useStates('commonModule', ['adminUserInfo']);
// 获取root中的state
useStates(['adminToken']);
// root和模块中的都能获取,自动补充模块前缀
useGetters(['commonModule/test', 'test']);
// root和模块中的都能获取,自动补充模块前缀
useMutations(['SET_ADMIN_TOKEN', 'commonModule/SET_ADMIN_USERINFO']);
// root和模块中的都能获取,自动补充模块前缀
useActions(['fn', 'commonModule/GETADMINUSERINFO_ACTION']);
Q: 看到这里就有同学会问了,这么长的字符串,每次都要手动输入不是很麻烦吗
A: 在我们实现类型增强的时候,IDE就有类型提示了,我们只需要选择即可,如下图所示,非常安全!!!

目录组织
store目录组织
module
里存放各各模块,如common
root
这里也认为是一个模块放在这里
types
存放一些类型和工具类型
index
store出口

hooks组织
实现4个hooks,useStates
useMutations
useGetters
useActions

实现
root模块搭建
const.ts
export class Mutations_Const {
static SET_ADMIN_TOKEN = 'SET_ADMIN_TOKEN' as const;
}
types.d.ts
import { ActionContext } from 'vuex';
import { Mutations_Const } from '@/store/modules/root/const';
type RootState = {
version: string;
adminToken: string;
};
type GettersInRoot = Record<string, (...args: any[]) => any>; // 测试数据
type MutationsInRoot = {
[Mutations_Const.SET_ADMIN_TOKEN](state: RootState, token: string): void; // 测试数据
};
type ActionsInRoot = {
fn(context: ActionContext<RootState, RootState>): void; // 测试数据
};
export { RootState, GettersInRoot, MutationsInRoot, ActionsInRoot };
index.ts
import type { InjectionKey } from 'vue';
import type { RootState } from '@/store/modules/root/types';
import { createStore, Store as VuexStore, useStore as useBaseStore } from 'vuex';
import { rootState, gettersInRoot, mutationsInRoot, actionsInRoot } from '@/store/modules/root';
// 创建一个具备类型安全的唯一注入 Key,用于在 Vue 组件中注入 Store 实例。一定需要
const key: InjectionKey<VuexStore<RootState>> = Symbol();
const store = createStore({
// root模块
state: () => rootState,
mutations: mutationsInRoot,
actions: actionsInRoot,
getters: gettersInRoot,
});
function useStore() {
return useBaseStore(key);
}
export { store, key, useStore };
在main.ts中使用即可
import { store, key } from '@/store';
import App from './App.vue';
const app = createApp(App);
app.use(store, key);
这样我们就创建好了一个不带类型提示的vuex仓库啦
common模块搭建
action-types.ts
两个都是用于保存常量的文件,可根据自己需要使用
export const GETPROFILE_ACTION = 'GETPROFILE_ACTION';
export const GETADMINUSERINFO_ACTION = 'GETADMINUSERINFO_ACTION';
mutation-types.ts
export const SET_PROFILE = 'SET_PROFILE';
export const SET_ADMIN_USERINFO = 'SET_ADMIN_USERINFO';
state.ts
state模块,定义一个类型和一个state对象,导出对象和类型, 具体类型朋友们可以忽略,直接自己定义几个字符串即可~
import type { Profile, AdminUserType } from '@/api/types';
interface State {
profile: Profile.ProfileDetailData;
adminUserInfo: AdminUserType.AdminUserInfo;
}
const state: State = {
profile: {
author: '',
avatar: '',
csdnHomepage: '',
giteeHomepage: '',
githubHomepage: '',
introduction: '',
logo: '',
name: '',
zhihuHomepage: '',
},
adminUserInfo: {
username: '',
},
};
export { state };
export type { State };
getters.ts
import type { GetterTree } from 'vuex';
// 刚刚写好的State类型,拿过来使用
import type { State } from './state';
// root模块的State
import type { RootState } from '@/store/modules/root/types';
// Getters 类型
type Getters = {
test(): Record<string, Array<number>>; // 测试类型,随便定义
};
// 类型实现
const getters: GetterTree<State, RootState> & Getters = {
test() {
const a = { a: [1] };
return a;
},
};
// 依旧导出
export { getters };
export type { Getters };
mutations.ts
import type { MutationTree } from 'vuex';
// 刚刚写好的State类型
import type { State } from './state';
import { SET_PROFILE, SET_ADMIN_USERINFO } from './mutation-types';
import type { Profile, AdminUserType } from '@/api/types';
// 这里使用上述定义的常量进行赋值,不容易错
type Mutations = {
[SET_PROFILE](state: State, payload: Profile.ProfileDetailData): void;
[SET_ADMIN_USERINFO](state: State, payload: AdminUserType.AdminUserInfo): void;
};
const mutations: MutationTree<State> & Mutations = {
[SET_PROFILE](state, payload) {
state.profile = payload;
},
[SET_ADMIN_USERINFO](state, payload) {
state.adminUserInfo = payload;
},
};
// 依旧导出
export { mutations };
export type { Mutations };
actions.ts
import type { State } from './state';
import type { ActionContext as VuexActionContext, ActionTree } from 'vuex';
import type { RootState } from '@/store/modules/root/types';
import { GETPROFILE_ACTION, GETADMINUSERINFO_ACTION } from './actions-types';
import API from '@/api';
// VuexActionContext泛型,传入定义的State和RootState 类型,好让context有类型推导
type CommonActionContext = VuexActionContext<State, RootState>;
type Actions = {
[GETPROFILE_ACTION](context: CommonActionContext): void;
[GETADMINUSERINFO_ACTION](context: CommonActionContext): void;
};
const actions: ActionTree<State, RootState> & Actions = {
async [GETPROFILE_ACTION](context) {
const profile = await API.profileDetail.getProfileDetail();
context.commit('SET_PROFILE', profile!.data);
},
async [GETADMINUSERINFO_ACTION](context) {
const adminUserInfo = await API.AdminUser.getAdminUserInfo();
context.commit('SET_ADMIN_USERINFO', adminUserInfo!.data);
},
};
export { actions };
export type { Actions };
index.ts
import type { Module } from 'vuex';
import type { RootState } from '@/store/modules/root/types';
import type { State } from './state';
// 导入上述定义的文件
import { state } from './state';
import { getters } from './getters';
import { mutations } from './mutations';
import { actions } from './actions';
const commonModule: Module<State, RootState> = {
namespaced: true, // 开启命名空间,独立
state: () => state, // 单独定义state是为了让ts验证State类型
getters,
mutations,
actions,
};
//导出模块对象
export default commonModule;
挂载到vuex中
const store = createStore({
state: () => rootState,
mutations: mutationsInRoot,
actions: actionsInRoot,
getters: gettersInRoot,
// 这里注册模块
modules: {
commonModule,
},
})
到这里,我们就搭建好了基本的的vuex了,接下来来增强其类型推导
types文件夹
root.d.ts
我们要增强其类型推导,就要实现一个增强后的仓库类型赋值给Store,如EnhancedStore
补充小知识,TS中,Omit类型工具可以对一个类型从该类型中去除指定的属性,extends可以继承某个类型
那么我们大概需要一个如下的伪代码
interface EnhancedStore extends Omit<Store, 'getters', 'commit', 'dispatch'> {
getters: xxx;
commit: xxx;
dispatch: xxx;
}
// 然后导出该类型
export { EnhancedStore }
types.d.ts
这个文件就用来写各种工具类型,来帮我们转换成想要的类型
// 首先处理state
// 因为state没有额外的处理,我们直接定义即可
// 访问需要store.state[namespace][key]
import { State as CommonState } from '@/store/modules/common/state';
type ModuleStates = {
commonModule: CommonState;
// otherModule: OtherState;
};
// 处理getters
// 开启了namespaced的module要访问,需要store.getters['modulename/key'],故我们要构造这样的结构
// 将模块化的Getters 映射为带模块名字的字面量:commonModule/name
// 泛型G是包含模块中getters的类型
type NamespacedGetters<ModuleName extends string, G> = {
[K in keyof G as `${ModuleName}/${string & K}`]: ReturnType<G[K]>;
};
// common模块中的Getters类型
import { Getters as CommonGetters } from '@/store/modules/common/getters';
// Root模块中的Getters类型
import { GettersInRoot } from '@/store/modules/root/types';
//使用工具类型对common模块转换得到新类型,root模块直接store.getters[key], 故不用改造,最后合并类型
type RootGetters = NamespacedGetters<'commonModule', CommonGetters> & GettersInRoot;
// 处理mutations
// 开启了namespaced的module要访问,需要store.commit['modulename/key'],故我们要构造这样的结构
// actions同理,故类型工具可以合并在一起
type StoreTypes = 'Actions' | 'Mutations';
// 将模块化的Actions/Mutations映射为带模块名的字面量
type NameSpacedActionsOrMutations<
ModuleName extends string,
AllTypes extends (...args: any[]) => any,
> = {
[K in keyof AllTypes as `${ModuleName}/${K}`]: AllTypes[K];
};
// 处理commit和dispatch的options
type GetTypesOptions<T extends StoreTypes> =
T extends Extract<StoreTypes, 'Mutations'> ? CommitOptions : DispatchOptions;
// 处理commit和action中,payload携带参数的类型与提示
// 意思是对应的commit/action函数,如果第二个参数payload为undefined,payload为可选,补充options
// 如果是不为undefined,则把推断的P当作payload的参数类型
type GetParametersTypes<Type extends StoreTypes, Fn> =
Parameters<Fn> extends [any, infer P]
? undefined extends P
? [payload?: undefined, options?: GetTypesOptions<Type>]
: [payload: P, options?: GetTypesOptions<Type>]
: [payload?: undefined, options?: GetTypesOptions<Type>];
// 使用起来!!!增强Store的commit和dispatch类型推导
// 泛型T为mutations或者actions对象,返回一个函数,第一个参数type为对应泛型T中的key,剩余参数由上述GetParametersTypes决定
type EnhanceStoreTypes<
Type extends StoreTypes,
T extends Record<string, (...args: any[]) => any>,
> = <K extends keyof T>(type: K, ...args: GetParametersTypes<Type, T[K]>) => ReturnType<T[K]>;
// 现在已经编写完类型工具了,可以直接来实现mutations和actions
import { Mutations as CommonMutations } from '@/store/modules/common/mutations';
import { MutationsInRoot } from '@/store/modules/root/types';
type RootMutations = NameSpacedActionsOrMutations<'commonModule', CommonMutations> &
MutationsInRoot;
type RootCommit = EnhanceStoreTypes<'Mutations', RootMutations>;
import { Actions as CommonActions } from '@/store/modules/common/actions';
import { ActionsInRoot } from '@/store/modules/root/types';
type RootActions = NameSpacedActionsOrMutations<'commonModule', CommonActions> & ActionsInRoot;
type RootDispatch = EnhanceStoreTypes<'Actions', RootActions>;
// 导出工具
export {
ModuleStates,
RootGetters,
RootCommit,
RootMutations,
RootDispatch,
RootActions,
StoreTypes,
GetTypesOptions,
};
到这里,已经完成编写类型了,可以回到root.d.ts完成增强后的store类型了
root.d.ts--update
import { Store as VuexStore } from 'vuex';
import { RootGetters, RootCommit, RootDispatch } from '@/store/types/types-helper';
interface EnhancedStore extends Omit<VuexStore<RootState>, 'getters' | 'commit' | 'dispatch'> {
getters: RootGetters;
commit: RootCommit;
dispatch: RootDispatch;
}
export { EnhancedStore };
index.ts--update
import type { InjectionKey } from 'vue';
import type { EnhancedStore } from './types/root';
import type { RootState } from '@/store/modules/root/types';
import { createStore, Store as VuexStore, useStore as useBaseStore } from 'vuex';
import commonModule from './modules/common';
import { rootState, gettersInRoot, mutationsInRoot, actionsInRoot } from '@/store/modules/root';
const key: InjectionKey<VuexStore<RootState>> = Symbol(); // 创建一个具备类型安全的唯一注入 Key,用于在 Vue 组件中注入 Store 实例。
const store = createStore({
state: () => rootState,
mutations: mutationsInRoot,
actions: actionsInRoot,
getters: gettersInRoot,
modules: {
commonModule,
},
}) as EnhancedStore;
function useStore(): EnhancedStore {
return useBaseStore(key);
}
export { store, key, useStore };
现在去使用store,就会具有对应的类型推导和提示了!!!!!!
hook实现
types.d.ts
import { ComputedRef } from 'vue';
import { StoreTypes, GetTypesOptions } from '@/store/types/types-helper';
// 处理mutations和actions函数的参数和函数的返回值类型
type GetFnParametersAndReturnType<Type extends StoreTypes, Fn> =
Parameters<Fn> extends [any, infer P]
? undefined extends P
? (payload?: undefined, options?: GetTypesOptions<Type>) => ReturnType<Fn>
: (payload: P, options?: GetTypesOptions<Type>) => ReturnType<Fn>
: (payload?: undefined, options?: GetTypesOptions<Type>) => ReturnType<Fn>;
// 根据mutations和actions对象,映射函数
type FnMapper<Type extends StoreTypes, T extends Record<string, (...args: any[]) => any>> = {
[K in keyof T]: GetFnParametersAndReturnType<Type, T[K]>;
};
// 因为state和getters需要包装成响应式的computedRef
type StateOrGettersMapper<T> = {
[K in keyof T]: ComputedRef<T[K]>;
};
// 导出
export { GetFnParametersAndReturnType, FnMapper, StateOrGettersMapper };
useStates.ts
import type { ComputedRef } from 'vue';
import type { RootState } from '@/store/modules/root/types';
import type { ModuleStates } from '@/store/types/types-helper';
import type { StateOrGettersMapper } from '@/hooks/store-hooks/types-helper';
import { computed } from 'vue';
import { useStore } from '@/store';
// 重载定义
// 根模块数组
function useStates<K extends keyof RootState>(keys: K[]): Pick<StateOrGettersMapper<RootState>, K>;
// 根模块别名
function useStates<M extends Record<string, keyof RootState>>(
keys: M
): { [K in keyof M]: ComputedRef<RootState[M[K]]> };
// 模块命名空间数组
function useStates<N extends keyof ModuleStates, K extends keyof ModuleStates[N]>(
namespace: N,
keys: K[]
): Pick<StateOrGettersMapper<ModuleStates[N]>, K>;
// 模块命名空间别名
function useStates<N extends keyof ModuleStates, M extends Record<string, keyof ModuleStates[N]>>(
namespace: N,
keys: M
): { [K in keyof M]: ComputedRef<ModuleStates[N][M[K]]> };
// 实现体
function useStates(
namespaceOrKeys:
| keyof RootState
| (keyof RootState)[]
| Record<string, keyof RootState>
| keyof ModuleStates,
keysOrMaybeNothing?: any
) {
const store = useStore();
const result: Record<string, ComputedRef<any>> = {};
if (
Array.isArray(namespaceOrKeys) ||
(typeof namespaceOrKeys === 'object' && namespaceOrKeys !== null)
) {
// 根模块
const keys = namespaceOrKeys;
if (Array.isArray(keys)) {
keys.forEach((key) => {
result[key as string] = computed(() => store.state[key]);
});
} else {
Object.entries(keys).forEach(([alias, key]) => {
result[alias] = computed(() => store.state[key]);
});
}
} else if (typeof namespaceOrKeys === 'string') {
const namespace = namespaceOrKeys as keyof ModuleStates;
const keys = keysOrMaybeNothing;
if (Array.isArray(keys)) {
keys.forEach((key) => {
result[key as string] = computed(
() => store.state[namespace][key as keyof ModuleStates[typeof namespace]]
);
});
} else {
Object.entries(keys).forEach(([alias, key]) => {
result[alias] = computed(
() => store.state[namespace][key as keyof ModuleStates[typeof namespace]]
);
});
}
}
return result;
}
export default useStates;
useGetters.ts
import type { ComputedRef } from 'vue';
import type { RootGetters } from '@/store/types/types-helper';
import type { StateOrGettersMapper } from '@/hooks/store-hooks/types-helper';
import { computed } from 'vue';
import { useStore } from '@/store';
// 重载定义
// 方式 1:传数组(返回 Pick 工具函数)
function useGetters<K extends keyof RootGetters>(
keys: K[]
): Pick<StateOrGettersMapper<RootGetters>, K>;
// 方式 2:传别名映射(返回别名类型)
function useGetters<M extends Record<string, keyof RootGetters>>(
keys: M
): { [K in keyof M]: ComputedRef<RootGetters[M[K]]> };
// 实现
function useGetters(keys: (keyof RootGetters)[] | Record<string, keyof RootGetters>) {
const store = useStore();
const result: Record<string, any> = {};
if (Array.isArray(keys)) {
keys.forEach((key) => {
result[key as string] = computed(() => store.getters[key]);
});
} else {
Object.entries(keys).forEach(([alias, key]) => {
result[alias] = computed(() => store.getters[key]);
});
}
return result;
}
export default useGetters;
useMutations.ts
import { useStore } from '@/store';
import type { CommitOptions } from 'vuex';
import type { RootMutations } from '@/store/types/types-helper';
import type { GetFnParametersAndReturnType, FnMapper } from '@/hooks/store-hooks/types-helper';
// 重载定义
// 方式 1:传数组(返回 Pick 工具函数)
function useMutations<K extends keyof RootMutations>(
keys: K[]
): Pick<FnMapper<'Mutations', RootMutations>, K>;
// 方式 2:传别名映射(返回别名类型)
function useMutations<M extends Record<string, keyof RootMutations>>(
keys: M
): {
[K in keyof M]: GetFnParametersAndReturnType<'Mutations', RootMutations[M[K]]>;
};
// 实现
function useMutations(keys: (keyof RootMutations)[] | Record<string, keyof RootMutations>) {
const store = useStore();
const result: Record<string, any> = {};
if (Array.isArray(keys)) {
keys.forEach((key) => {
result[key as string] = (payload?: any, options?: CommitOptions) =>
store.commit(key as keyof RootMutations, payload, options);
});
} else {
Object.entries(keys).forEach(([alias, key]) => {
result[alias] = (payload?: any, options?: CommitOptions) =>
store.commit(key, payload, options);
});
}
return result;
}
/**
* 含有payload的mutation使用时,不传会报错
* 定义时未定义的则为可选,undefined
*
*/
export default useMutations;
useActions.ts
import { useStore } from '@/store';
import type { DispatchOptions } from 'vuex';
import type { RootActions } from '@/store/types/types-helper';
import type { GetFnParametersAndReturnType, FnMapper } from '@/hooks/store-hooks/types-helper';
// 重载定义
// 1. 数组形式,返回 Pick
function useActions<K extends keyof RootActions>(
keys: K[]
): Pick<FnMapper<'Actions', RootActions>, K>;
// 2. 别名映射形式,返回对应别名映射
function useActions<M extends Record<string, keyof RootActions>>(
keys: M
): {
[K in keyof M]: GetFnParametersAndReturnType<'Actions', RootActions[M[K]]>;
};
// 实现
function useActions(keys: (keyof RootActions)[] | Record<string, keyof RootActions>) {
const store = useStore();
const result: Record<string, any> = {};
if (Array.isArray(keys)) {
keys.forEach((key) => {
result[key as string] = (payload?: any, options?: DispatchOptions) =>
store.dispatch(key as keyof RootActions, payload, options);
});
} else {
Object.entries(keys).forEach(([alias, key]) => {
result[alias] = (payload?: any, options?: DispatchOptions) =>
store.dispatch(key, payload, options);
});
}
return result;
}
export default useActions;
index.ts
import useStates from '@/hooks/store-hooks/useStates';
import useGetters from '@/hooks/store-hooks/useGetters';
import useMutations from '@/hooks/store-hooks/useMutations';
import useActions from '@/hooks/store-hooks/useActions';
export { useStates, useGetters, useMutations, useActions };
这样子我们就封装好了,就可以和开头一样使用啦,有安全的类型提示啦!!!!
