What will the new version of Vuex be?

Vuex is a state manager for Vue applications. Its next version is Vuex 4, which is almost ready for official release. It will add support for Vue 3, but will not bring any new functionality.



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.



All Articles