Quelle sera la nouvelle version de Vuex?

Vuex est un gestionnaire d'état pour les applications Vue. Sa prochaine version est Vuex 4, qui est presque prête pour la sortie officielle. Il ajoutera la prise en charge de Vue 3, mais n'apportera aucune nouvelle fonctionnalité.



Bien que Vuex soit considéré comme une excellente solution et que de nombreux développeurs la choisissent comme bibliothèque de gestion d'état principale, ils espèrent obtenir plus de fonctionnalités dans les versions futures. Par conséquent, alors que Vuex 4 se prépare juste pour la sortie, l'un de ses développeurs, Kia King Ishii (qui fait partie de l'équipe de base) partage déjà les plans pour la prochaine version 5. Il est à noter que ce ne sont que des plans et que certaines choses peuvent changer, néanmoins, la direction principale a déjà été choisie. Nous parlerons de lui.



Avec l'avènement de Vue 3 et de l' API Composition , les développeurs ont commencé à créer des alternatives simples. Par exemple, l'article «Vous n'avez probablement pas besoin de Vuex » montre un moyen simple, flexible et fiable de créer des magasins basés sur l'API Composition en conjonction avec provide/inject



. Nous pouvons supposer que cela et d'autres alternatives conviennent aux petites applications, mais comme cela arrive souvent, elles ont leurs inconvénients: documentation, communauté, convention de dénomination, intégration, outils de développement.







Le dernier point est très important. Vue a maintenant une excellente extension de navigateurpour aider au développement et au débogage. L'abandonner peut être un gros gaspillage, en particulier lors de la construction de grandes applications. Heureusement, cela ne se produira pas avec Vuex 5. Quant aux approches alternatives, elles fonctionneront, mais elles n'apporteront pas autant d'avantages que la solution officielle. Voyons donc quels types d'avantages ils nous promettent.



Créer un magasin



Avant de faire quoi que ce soit avec le côté, nous devons le créer. Dans Vuex 4, cela ressemble à ceci:



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')
    }
  }
})

      
      





Le magasin se compose également de 4 parties: l'état, où les données sont stockées; getters qui fournissent des états calculés; les mutations nécessaires pour changer l'état et les actions qui sont appelées en dehors du magasin pour y effectuer des opérations. Habituellement, les actions ne provoquent pas seulement une mutation (comme dans l'exemple), mais sont utilisées pour exécuter des tâches asynchrones (car les mutations doivent être synchrones ) ou implémenter une logique plus complexe. À quoi ressemblera Vuex 5?



import { defineStore } from 'vuex'

export const counterStore = defineStore({
  name: 'counter',
  
  state() {
    return { count: 0 }
  },
  
  getters: {
    double () {
      return this.count * 2
    }
  },
  
  actions: {
    increment () {
      this.count++
    }
  }
})

      
      





La première chose qui a changé est le changement createStore



de nom en defineStore



. Un peu plus tard, on comprendra pourquoi. Ensuite, il y avait un paramètre name



pour spécifier le nom du magasin. Avant cela, nous avons divisé les côtés en modules, et les noms de modules étaient sous la forme d'objets nommés. De plus, les modules ont été enregistrés dans l'espace mondial, raison pour laquelle ils n'étaient pas autonomes et prêts à être réutilisés. En guise de solution, un paramètre devait être utilisé namespaced



pour empêcher plusieurs modules de répondre au même type de mutations et d'actions. Je pense que beaucoup l'ont rencontré, mais j'ajouterai néanmoins un lien vers la documentation . Maintenant, nous n'avons plus de modules, chaque magasin est par défaut un stockage séparé et indépendant.



Après avoir spécifié le nom, nous devons en faire une state



fonction qui renvoie l'état initial, pas seulement le définit. Ceci est très similaire à ce à quoi il ressemble data



dans les composants. Les modifications ont également affecté les getters, au lieu d' state



utiliser une fonction comme paramètre this



pour accéder aux données. La même approche est appliquée aux actions this



plutôt stat



qu'en tant que paramètre. Enfin, et surtout, les mutations sont associées à des jeux d'action. Kia a célébréque les mutations deviennent assez souvent de simples setters, les rendant verbeuses, apparemment c'était la raison de la suppression. Il ne précise pas s'il sera possible de faire des changements d'état en dehors du magasin, par exemple à partir de composants. Ici, nous ne pouvons que faire référence au modèle Flux, qui ne recommande pas de le faire et encourage une approche de changement d'état à partir des actions.



Addendum: Ceux qui utilisent l'API de composition pour créer des composants seront heureux de savoir qu'il existe un moyen de créer un magasin de la même manière.



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 }  
})

      
      





Dans l'exemple ci-dessus, nous avons passé le nom du magasin comme premier argument defineStore



. Le reste est l'API de composition habituelle, et le résultat sera exactement le même que dans l'exemple avec l'API classique.



Initialisation du magasin



Des changements importants nous attendent ici. Pour décrire comment l'initialisation du magasin se déroulera dans la 5ème version, voyons comment cela se passe dans la 4ème. Lorsque nous créons un magasin via createStore



, nous l'initialisons immédiatement pour pouvoir ensuite l'utiliser app.use



directement ou directement.



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

      
      





Dans la 5ème version, nous accédons séparément à chaque instance de Vuex, ce qui garantit l'indépendance. Par conséquent, ce processus est différent:



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')

      
      





Tous les composants ont désormais la possibilité d'accéder directement à n'importe quelle instance de Vuex, au lieu d'accéder à l'espace global. Jetez un œil à un exemple:



import { defineComponent } from 'vue'
import store from './store'

export default defineComponent({
  name: 'App',

  computed: {
    counter () {
      return this.$vuex.store(store)
    }
  }
})

      
      





L'appel $vuex.store



crée et initialise le magasin (n'oubliez pas de renommer createStore



). Désormais, chaque fois que vous communiquez avec ce référentiel via $vuex.store



, vous retournerez une instance déjà créée. Dans l'exemple, c'est ce this.counter



que nous pouvons utiliser plus loin dans le code. Vous pouvez également initialiser le magasin via createVuex()



.



Et bien sûr, une option pour l'API de composition, où est $vuex.store



utilisée à la place useStore



.



import { defineComponent } from 'vue'
import { useStore } from 'vuex'
import store from './store'

export default defineComponent({
  setup () {
    const counter = useStore(store)

    return { counter }
  }
})

      
      





L'approche décrite ci-dessus (initialisation du magasin via des composants) présente à la fois des avantages et des inconvénients. D'une part, il s'agit de la séparation du code et de la possibilité de l'ajouter uniquement là où c'est nécessaire. D'autre part, ajouter une dépendance (vous devez maintenant importer le magasin à chaque fois que vous prévoyez de l'utiliser). Par conséquent, si vous souhaitez utiliser DI, alors une option utilisant 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')

      
      





Et puis jeter le magasin dans le composant (on souhaite le remplacer name



par une constante et déjà l'utiliser):



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 }
  }
})

      
      





Il n'y a pas beaucoup d'enthousiasme pour cette solution, mais elle semble plus explicite et flexible que l'approche actuelle. On ne peut pas en dire autant de l’utilisation ultérieure. Maintenant, cela ressemble à ceci:



store.state.count            // State
store.getters.double         // Getters
store.commit('increment')    // Mutations
store.dispatch('increment')  // Actions

      
      





La nouvelle version devrait:



store.count        // State
store.double       // Getters
store.increment()  // Actions

      
      





Toutes les entités (état, getters et actions) sont disponibles directement, ce qui facilite leur utilisation et la rend plus logique. En même temps , il supprime la nécessité mapState



, mapGetters



, mapActions



et mapMutations



, ainsi que l' écriture calculées supplémentaires propriétés.



Partage



Le dernier point à considérer est le partage. Nous nous souvenons que dans Vuex 5 nous n'avons plus de modules nommés et que chaque côté est séparé et indépendant. Cela permet de les importer en cas de besoin et d'utiliser les données selon les besoins, tout comme les composants. Une question logique se pose, comment utiliser plusieurs magasins ensemble? Dans la version 4, il existe toujours un espace de noms global et nous devons utiliser rootGetters



et rootState



faire référence à différents magasins dans cette portée (comme dans la version 3). L'approche de Vuex 5 est différente:



// 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}'
    }
  }
})
      
      





Nous importons le magasin, puis l'enregistrons use



et y accédons. Tout semble encore plus simple si vous utilisez l'API de composition:



// 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 }
})

      
      





La seule chose qui mérite d'être mentionnée est qu'il use



fonctionne exactement de la même manière vuex.store



et est responsable de l'initialisation correcte des magasins.



Prise en charge de TypeScript



Avec les changements d'API et moins d'abstractions, la prise en charge de TypeScript dans la version 4 sera bien meilleure, mais nous avons encore besoin de beaucoup de travail manuel. La sortie de la version 5 va permettre d'ajouter des types là où c'est nécessaire, et là où on le veut.



Conclusion



Vuex 5 semble prometteur et est exactement ce à quoi beaucoup s'attendent (correction d'anciens bogues, ajout de flexibilité). Une liste complète des discussions et des vues de l'équipe principale peut être trouvée dans le référentiel Vue RFCs .



All Articles