While Vuex is considered a great solution and many developers choose it as their primary state management library, they hope to get more features in future releases. Therefore, while Vuex 4 is just getting ready for release, one of its developers, Kia King Ishii (part of the core team) is already sharing plans for the next, 5 version. It is worth noting that these are only plans and some things may change, nevertheless, the main direction has already been chosen. We will talk about him.
With the advent of Vue 3 and the Composition API , developers began to create simple alternatives. For example, the article “You Probably Don't Need Vuex ” demonstrates a simple, flexible and reliable way to create Composition API-based stores in conjunction with
provide/inject
. We can assume that this and some other alternatives are fine for small applications, but as often happens, they have their drawbacks: documentation, community, naming conventions, integration, developer tools.
The last point is very important. Vue now has a great browser extensionto assist with development and debugging. Ditching it can be a big loss, especially when building large applications. Fortunately, this won't happen with Vuex 5. As for the alternative approaches, they will work, but they will not bring as many benefits as the official solution. Therefore, let's see what kind of advantages they promise us.
Creating a store
Before doing anything with the side, we need to create it. In Vuex 4, it looks like this:
import { createStore } from 'vuex'
export const counterStore = createStore({
state: {
count: 0
},
getters: {
double (state) {
return state.count * 2
}
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
The store also consists of 4 parts: state, where data is stored; getters that provide computed states; mutations (mutations) required to change state and actions (actions) that are called outside the store to perform operations on it. Usually, actions do not just cause mutation (as in the example), but are used to perform asynchronous tasks (because mutations must be synchronous ) or implement some more complex logic. What will Vuex 5 look like?
import { defineStore } from 'vuex'
export const counterStore = defineStore({
name: 'counter',
state() {
return { count: 0 }
},
getters: {
double () {
return this.count * 2
}
},
actions: {
increment () {
this.count++
}
}
})
The first thing that has changed is the renaming
createStore
to
defineStore
. A little later it will be clear why. Next, there was a parameter
name
for specifying the name of the store. Before that, we divided the sides into modules, and the module names were in the form of named objects. Further, the modules were registered in the global space, which is why they were not self-sufficient and ready for reuse. As a solution, a parameter had to be used
namespaced
to prevent multiple modules from responding to the same type of mutations and actions. I think many have come across this, but I will nevertheless add a link to the documentation . Now we do not have modules, each store is by default a separate and independent storage.
After specifying the name, we need to make it a
state
function that returns the initial state, not just sets it. This is very similar to what it looks like
data
in components. The changes also affected the getters, instead of
state
using a function as a parameter
this
to access data. The same approach is applied to actions
this
instead of
stat
as a parameter. Finally, and most importantly, mutations are combined with action games. Kia celebratedthat mutations quite often become simple setters, making them verbose, apparently this was the reason for the deletion. He does not mention whether it will be possible to make state changes outside the store, for example from components. Here, we can only refer to the Flux pattern, which does not recommend doing this and encourages a state-changing approach from actions.
Addition: those who use the Composition API to create components will be happy to know that there is a way to create a store in a similar manner.
import { ref, computed } from 'vue'
import { defineStore } from 'vuex'
export const counterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment () {
count.value++
}
return { count, double, increment }
})
In the example above, we passed the store name as the first argument
defineStore
. The rest is the usual Composition API, and the result will be exactly the same as in the example with the classic API.
Store initialization
Significant changes await us here. To describe how the initialization of the store will take place in the 5th version, let's see how it happens in the 4th. When we create a store via
createStore
, we immediately initialize it, so that we can then use it in
app.use
or directly.
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
// `this.$store`
// `useStore()` Composition API
import store from './store'
store.state.count // -> 0
store.commit('increment')
store.dispatch('increment')
store.getters.double // -> 4
In the 5th version, we separately access each Vuex instance, which guarantees independence. Therefore, this process looks different:
import { createApp } from 'vue'
import { createVuex } from 'vuex'
import App from './App.vue'
const app = createApp(App)
const vuex = createVuex()
app.use(vuex)
app.mount('#app')
All components now have the ability to access any Vuex instance directly, instead of accessing the global space. Take a look at an example:
import { defineComponent } from 'vue'
import store from './store'
export default defineComponent({
name: 'App',
computed: {
counter () {
return this.$vuex.store(store)
}
}
})
The call
$vuex.store
creates and initializes the store (remember about renaming
createStore
). Now, each time communicating to this repository through
$vuex.store
, the already created instance will be returned to you. In the example, this
this.counter
is which we can use further in the code. You can also initialize the store via
createVuex()
.
And of course, an option for the Composition API, where is
$vuex.store
used instead
useStore
.
import { defineComponent } from 'vue'
import { useStore } from 'vuex'
import store from './store'
export default defineComponent({
setup () {
const counter = useStore(store)
return { counter }
}
})
The approach described above (initializing the store through components) has both advantages and disadvantages. On the one hand, this is the separation of the code and the ability to add it only where necessary. On the other hand, adding a dependency (now you need to import the store every time you plan to use it). Therefore, if you want to use DI, then an option using
provide
:
import { createApp } from 'vue'
import { createVuex } from 'vuex'
import App from './App.vue'
import store from './store'
const app = createApp(App)
const vuex = createVuex()
app.use(vuex)
app.provide('name', store)
app.mount('#app')
And then throwing the store into the component (there is a desire to replace it
name
with a constant and already use it):
import { defineComponent } from 'vue'
export default defineComponent({
name: 'App',
inject: ['name']
})
// Composition API
import { defineComponent, inject } from 'vue'
export default defineComponent({
setup () {
const store = inject('name')
return { store }
}
})
There isn't much enthusiasm for this solution, but it looks more explicit and flexible than the current approach. The same cannot be said about further use. Now it looks like this:
store.state.count // State
store.getters.double // Getters
store.commit('increment') // Mutations
store.dispatch('increment') // Actions
The new version is expected to:
store.count // State
store.double // Getters
store.increment() // Actions
All entities (state, getters, and actions) are available directly, making working with them easier and more logical. At the same time it removed the need for
mapState
,
mapGetters
,
mapActions
and
mapMutations
, as well as writing additional computed properties.
Sharing
The last point to consider is sharing. We remember that in Vuex 5 we no longer have named modules and each side is separate and independent. This makes it possible to import them when needed and use the data as needed, just like components. A logical question arises, how to use several stores together? In version 4 there is still a global namespace and we need to use
rootGetters
and
rootState
to refer to different stores in this scope (just like in version 3). The approach in Vuex 5 is different:
// store/greeter.js
import { defineStore } from 'vuex'
export default defineStore({
name: 'greeter',
state () {
return { greeting: 'Hello' }
}
})
// store/counter.js
import { defineStore } from 'vuex'
import greeterStore from './greeter'
export default defineStore({
name: 'counter',
use () {
return { greeter: greeterStore }
},
state () {
return { count: 0 }
},
getters: {
greetingCount () {
return `${this.greeter.greeting} ${this.count}'
}
}
})
We import the store, then register it with
use
and thereby access it. Everything looks even easier if you use the Composition API:
// store/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'vuex'
import greeterStore from './greeter'
export default defineStore('counter', ({use}) => {
const greeter = use(greeterStore)
const count = 0
const greetingCount = computed(() => {
return `${greeter.greeting} ${this.count}`
})
return { count, greetingCount }
})
The only thing worth mentioning is that it
use
works exactly the same way
vuex.store
and is responsible for the correct initialization of the stores.
TypeScript support
With API changes and fewer abstractions, TypeScript support in version 4 will be much better, but we still need a lot of manual work. The release of version 5 will make it possible to add types where necessary, and where we want it.
Conclusion
Vuex 5 looks promising and is exactly what many expect (fixing old bugs, adding flexibility). A full list of core team discussions and views can be found in the Vue RFCs repository.