Skip to content

Instantly share code, notes, and snippets.

@baryla
Last active March 7, 2023 06:45
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baryla/dab7ebb745623dc70ff2d21f474591a1 to your computer and use it in GitHub Desktop.
Save baryla/dab7ebb745623dc70ff2d21f474591a1 to your computer and use it in GitHub Desktop.
Vue 3 + Vuex 4 - useStoreModule composable
import { useStore, Store } from "vuex";
interface InternalModule<S, A, M, G> {
state: S;
actions: A;
mutations: M;
getters: G;
}
/**
* This function allows us to access the internal vuex properties and
* maps them in a way which removes the module prefix.
*/
function getFromStoreByType<T>(
moduleName: string,
type: unknown,
isNamespaced: boolean
) {
if (isNamespaced) {
return Object.keys(type)
.filter((t) => t.startsWith(`${moduleName}/`))
.reduce((acc, curr) => {
const typeName = curr.split("/").pop();
const typeValue = type[curr][0];
return { [typeName]: typeValue, ...acc };
}, {}) as T;
}
return Object.keys(type).reduce((acc, curr) => {
const typeValue = type[curr][0];
return { [curr]: typeValue, ...acc };
}, {}) as T;
}
/**
* We have to wrap the getters in a Proxy because we only want to
* "access" the getter if it actually being accessed.
*
* We could technically use the getFromStoreByType function but
* the getter would be invoked multiple types on store instantiation.
*
* This is just a little cheeky workaround. Proxy <3
*/
function wrapGettersInProxy<G>(
moduleName: string,
getters: G,
isNamespaced: boolean
) {
return new Proxy(getters as Object, {
get(_, prop: string) {
if (isNamespaced) {
return getters[`${moduleName}/${prop}`];
}
return getters[prop];
},
}) as G;
}
function isModuleNamespaced<S>(moduleName: string, store: Store<S>): boolean {
// @ts-ignore internal Vuex object that isn't typed.
return Boolean(store._modulesNamespaceMap[`${moduleName}/`]);
}
export default function useStoreModule<S = any, A = any, M = any, G = any>(
moduleName: string,
storeName?: string
): InternalModule<S, A, M, G> {
// @ts-ignore useStore doesn't yet accept a key as arg
const store = storeName ? useStore(storeName) : useStore();
const state = store.state[moduleName];
const isNamespaced = isModuleNamespaced(moduleName, store);
const actions = getFromStoreByType<A>(
moduleName,
// @ts-ignore internal Vuex object that isn't typed.
store._actions,
isNamespaced
);
const mutations = getFromStoreByType<M>(
moduleName,
// @ts-ignore internal Vuex object that isn't typed.
store._mutations,
isNamespaced
);
const getters = wrapGettersInProxy<G>(moduleName, store.getters, isNamespaced);
return {
actions,
mutations,
state,
getters,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment