Last active
November 26, 2020 07:41
-
-
Save ricardopadua/54fe24445b7aa73121d275f08a3ea38e to your computer and use it in GitHub Desktop.
Vue 3 + Vuex 4 - useStoreModule composable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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