Now that Vue 3 and Vuex 4 official releases are around the corner, I took this opportunity to start rewriting one of my side projects in (you guessed it) Vue 3!
Vue 3 introduces the Composition API (which is also available in Vue 2 as a plugin) which opens up a whole range of possibilities. It's probably worth reading the docs first to be able understand why composables are nice and super powerful but without wasting anymore of your time, let's get into it.
This is inspired by a Github issue on the Vuex repo.
The problem
Vuex supports the idea of splitting your store into modules which is something you almost definitely want to do if you're working on a large project as it makes your state more manageable and easier to work with. Along with modules, you almost always namespace them to avoid any naming clashes by containing the module in a namespace.
In order to access any of the properties of your module in a Vue component, you will either have to use the global $store
property on this
object or map them as computed properties or methods which would look something like this:
<template>
<h1>{{ greeting }}</h1>
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapActions } = createNamespacedHelpers('my-module');
export default {
computed: {
...mapState({
greeting: state => state.greeting
})
},
methods: {
...mapActions([
'setGreeting'
])
},
mounted () {
setTimeout(() => {
setGreeting('Hi there ๐');
}, 5000)
}
}
</script>
It's not too bad for a SFC component but you have to be very explicit in what you want to return which doesn't make this a nice DX.
The documentation for Vuex 4 is still a little vague and I didn't have enough time to investigate how to properly use Vuex in a functional component but it'll be very similar to the SFC one ๐คทโโ๏ธ
The solution
To fix this problem, I created a little composable which hooks into Vuex's internal properties and allows you to interact with your state
, actions
, mutations
and getters
.
It can be used like this if you had one store:
import { defineComponent } from 'vue';
import { useStoreModule } from './composables';
import { State, Actions } from './state';
const Greeting = defineComponent(() => {
const { state, actions } = useStoreModule<State, Actions>('my-module');
setTimeout(() => {
actions.setGreeting('Hi there ๐');
}, 5000);
return () => <h1>{state.greeting}</h1>;
});
export default Greeting;
If you had multiple stores, useStoreModule
accepts a second argument which would be the name of the store. So like this:
import { useStoreModule } from './composables';
import { State } from './state';
const { state } = useStoreModule<State>('my-module', 'store-name');
The useStoreModule
composable is available as a gist ๐
Let me know if you find it helpful ๐ฅ ๐
Top comments (2)
Hey, this seems to be exactly what I need :).
Could you provide a bit more comprehensive description of how to use this though? I'm failing to get it to work properly. What kind of types are you exporting from
./state
? Right now I'm just exporting the type like this:But it doesn't seem to work, in my SFC getters will just get
any
type and expect me to pass in the store. When ignoring that the getter in my component still will not update. The state does update though.Hey sorry about the late reply. It looks like there was a minor bug in the types for getters. If you haven't fixed it already, can you give the gist another try? Essentially, even though the types was passed through to the composable, it wasn't casted to the getters proxy function.
Before:
After: