Qu'arrive-t-il à VueX ?
Publié: 2022-03-10Vuex est la solution de gestion d'état dans les applications Vue. La prochaine version - Vuex 4 - franchit les dernières étapes avant de sortir officiellement. Cette version apportera une compatibilité totale avec Vue 3, mais n'ajoutera pas de nouvelles fonctionnalités. Bien que Vuex ait toujours été une solution puissante et le premier choix de nombreux développeurs pour la gestion de l'état dans Vue, certains développeurs espéraient voir davantage de problèmes de flux de travail résolus. Cependant, alors même que Vuex 4 vient de sortir, Kia King Ishii (un membre de l'équipe principale de Vue) parle de ses plans pour Vuex 5, et je suis tellement excité par ce que j'ai vu que je devais le partager avec vous tous. Notez que les plans de Vuex 5 ne sont pas finalisés, donc certaines choses peuvent changer avant la sortie de Vuex 5, mais si cela ressemble à ce que vous voyez dans cet article, cela devrait être une grande amélioration pour l'expérience du développeur.
Avec l'avènement de Vue 3 et de son API de composition, les gens se sont penchés sur des alternatives simples construites à la main. Par exemple, vous n'avez peut-être pas besoin de Vuex illustre un modèle relativement simple, mais flexible et robuste pour utiliser l'API de composition avec provide/inject
pour créer des magasins d'état partagés. Comme Gabor l'indique dans son article, cependant, cela (et d'autres alternatives) ne devrait être utilisé que dans des applications plus petites car il leur manque tout ce qui ne concerne pas directement le code : support de la communauté, documentation, conventions, bonnes intégrations Nuxt et développeur outils.
Ce dernier a toujours été l'un des plus gros problèmes pour moi. L'extension de navigateur Vue devtools a toujours été un outil incroyable pour le débogage et le développement d'applications Vue, et perdre l'inspecteur Vuex avec "voyage dans le temps" serait une perte assez importante pour le débogage de toutes les applications non triviales.
Heureusement, avec Vuex 5, nous pourrons avoir notre gâteau et le manger aussi. Cela fonctionnera plus comme ces alternatives d'API de composition mais conservera tous les avantages de l'utilisation d'une bibliothèque de gestion d'état officielle. Voyons maintenant ce qui va changer.
Définir un magasin
Avant de pouvoir faire quoi que ce soit avec un magasin Vuex, nous devons en définir un. Dans Vuex 4, une définition de magasin ressemblera à 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') } } })
Chaque magasin comporte quatre parties : state
stocke les données, les getters
vous donnent l'état calculé, les mutations
sont utilisées pour muter l'état et les actions
sont les méthodes appelées de l'extérieur du magasin pour accomplir tout ce qui concerne le magasin. Habituellement, les actions ne se contentent pas de commettre une mutation comme le montre cet exemple. Au lieu de cela, ils sont utilisés pour effectuer des tâches asynchrones car les mutations doivent être synchrones ou implémentent simplement des fonctionnalités plus compliquées ou en plusieurs étapes. Les actions ne peuvent pas non plus faire muter l'état par elles-mêmes ; ils doivent utiliser un mutateur. Alors à quoi ressemble 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++ } } })
Il y a quelques changements à noter ici. Tout d'abord, au lieu de createStore
, nous utilisons defineStore
. Cette différence est négligeable, mais elle est là pour des raisons sémantiques, sur lesquelles nous reviendrons plus tard. Ensuite, nous devons fournir un name
pour le magasin, dont nous n'avions pas besoin auparavant. Dans le passé, les modules avaient leur propre nom, mais ils n'étaient pas fournis par le module lui-même ; il s'agissait simplement du nom de propriété auquel ils avaient été attribués par le magasin parent qui les avait ajoutés. Maintenant, il n'y a plus de modules . Au lieu de cela, chaque module sera un magasin séparé et aura un nom. Ce nom est utilisé par le registre Vuex, dont nous parlerons plus tard.
Après cela, nous devons faire de state
une fonction qui renvoie l'état initial au lieu de simplement la définir à l'état initial. Ceci est similaire à l'option de data
sur les composants. Nous écrivons des getters
très similaires à la façon dont nous l'avons fait dans Vuex 4, mais au lieu d'utiliser l' state
comme paramètre pour chaque getter, vous pouvez simplement this
utiliser pour accéder à l'état. De la même manière, les actions
n'ont pas à s'inquiéter de la transmission d'un objet de context
: elles peuvent simplement this
utiliser pour accéder à tout. Enfin, il n'y a pas de mutations
. Au lieu de cela, les mutations sont combinées avec des actions
. Kia a noté que trop souvent, les mutations devenaient de simples setters, les rendant inutilement verbeuses, alors ils les supprimaient. Il n'a pas mentionné s'il était "ok" de muter l'état directement depuis l'extérieur du magasin, mais nous sommes définitivement autorisés et encouragés à muter l'état directement à partir d'une action et le modèle Flux désapprouve la mutation directe de l'état.
Remarque : Pour ceux qui préfèrent l'API de composition à l'API d'options pour la création de composants, vous serez heureux d'apprendre qu'il existe également un moyen de créer des magasins de la même manière qu'avec l'API de composition.
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 } })
Comme indiqué ci-dessus, le nom est transmis comme premier argument pour defineStore
. Le reste ressemble à une fonction de composition pour les composants. Cela donnera exactement le même résultat que l'exemple précédent qui utilisait l'API options.
Obtenir le magasin instancié
Dans Vuex 4, les choses ont changé par rapport à Vuex 3, mais je vais juste regarder la v4 pour éviter que les choses ne deviennent incontrôlables. Dans la v4, lorsque vous avez appelé createStore
, vous l'avez déjà instancié. Vous pouvez ensuite simplement l'utiliser dans votre application, soit via app.use
, soit directement :
import { createApp } from 'vue' import App from './App.vue' // Your root component import store from './store' // The store definition from earlier const app = createApp(App) app.use(store) app.mount('#app') // Now all your components can access it via `this.$store` // Or you can use in composition components with `useStore()` // ----------------------------------------------- // Or use directly... this is generally discouraged import store from './store' store.state.count // -> 0 store.commit('increment') store.dispatch('increment') store.getters.double // -> 4
C'est une chose que Vuex 5 rend un peu plus compliquée que dans la v4. Chaque application peut désormais obtenir une instance distincte de Vuex, ce qui garantit que chaque application peut avoir des instances distinctes des mêmes magasins sans partager de données entre elles. Vous pouvez partager une instance de Vuex si vous souhaitez partager des instances de magasins entre les applications.
import { createApp } from 'vue' import { createVuex } from 'vuex' import App from './App.vue' // Your root component const app = createApp(App) const vuex = createVuex() // create instance of Vuex app.use(vuex) // use the instance app.mount('#app')
Désormais, tous vos composants ont accès à l'instance Vuex. Au lieu de donner directement la définition de votre ou vos boutiques, vous les importez ensuite dans les composants dans lesquels vous souhaitez les utiliser et utilisez l'instance Vuex pour les instancier et les enregistrer :
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
Appeler $vuex.store
, instancie et enregistre le magasin dans l'instance Vuex. À partir de ce moment, chaque fois que vous utiliserez $vuex.store
sur ce magasin, il vous rendra le magasin déjà instancié au lieu de l'instancier à nouveau. Vous pouvez appeler la méthode store
directement sur une instance de Vuex créée par createVuex()
.
Votre boutique est maintenant accessible sur ce composant via this.counter
. Si vous utilisez l'API de composition pour votre composant, vous pouvez utiliser useStore
au lieu de this.$vuex.store
:
import { defineComponent } from 'vue' import { useStore } from 'vuex' // import useStore import store from './store' export default defineComponent({ setup () { const counter = useStore(store) return { counter } } })
Il y a des avantages et des inconvénients à importer le magasin directement dans le composant et à l'instancier là-bas. Il vous permet de fractionner le code et de charger paresseusement le magasin uniquement là où il est nécessaire, mais il s'agit désormais d'une dépendance directe au lieu d'être injectée par un parent (sans oublier que vous devez l'importer chaque fois que vous souhaitez l'utiliser). Si vous souhaitez utiliser l'injection de dépendances pour la fournir dans toute l'application, en particulier si vous savez qu'elle sera utilisée à la racine de l'application où le fractionnement du code n'aidera pas, vous pouvez simplement utiliser 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('store', store) // provide the store to all components app.mount('#app')
Et vous pouvez simplement l'injecter dans n'importe quel composant où vous allez l'utiliser :
import { defineComponent } from 'vue' export default defineComponent({ name: 'App', inject: ['store'] }) // Or with Composition API import { defineComponent, inject } from 'vue' export default defineComponent({ setup () { const store = inject('store') return { store } } })
Je ne suis pas enthousiasmé par cette verbosité supplémentaire, mais elle est plus explicite et plus flexible, ce dont je suis fan. Ce type de code est généralement écrit une fois tout de suite au début du projet et cela ne vous dérange plus, bien que maintenant vous devrez soit fournir chaque nouveau magasin, soit l'importer chaque fois que vous souhaitez l'utiliser, mais importer ou injecter des modules de code est la façon dont nous devons généralement travailler avec quoi que ce soit d'autre, il s'agit donc simplement de faire fonctionner Vuex davantage dans le sens de la façon dont les gens ont déjà tendance à travailler.
Utilisation d'un magasin
En plus d'être un fan de la flexibilité et de la nouvelle façon de définir les magasins de la même manière qu'un composant utilisant l'API de composition, il y a une autre chose qui me rend plus excité que tout le reste : la façon dont les magasins sont utilisés. Voici à quoi ressemble l'utilisation d'un magasin dans Vuex 4.
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
State
, getters
, mutations
et actions
sont tous gérés de différentes manières via différentes propriétés ou méthodes. Cela a l'avantage de l'explicitation, dont j'ai fait l'éloge tout à l'heure, mais cette explicité ne nous rapporte vraiment rien. Et cette API devient plus difficile à utiliser lorsque vous utilisez des modules avec espace de noms. En comparaison, Vuex 5 semble fonctionner exactement comme vous l'espérez normalement :
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
Tout - l'état, les getters et les actions - est disponible directement à la racine du magasin, ce qui le rend simple à utiliser avec beaucoup moins de verbosité et supprime pratiquement tout besoin d'utiliser mapState
, mapGetters
, mapActions
et mapMutations
pour les options API ou pour l'écriture instructions computed
supplémentaires ou fonctions simples pour l'API de composition. Cela donne simplement à un magasin Vuex l'apparence et le comportement d'un magasin normal que vous construiriez vous-même, mais il bénéficie de tous les avantages des plugins, des outils de débogage, de la documentation officielle, etc.
Composer des magasins
Le dernier aspect de Vuex 5 que nous aborderons aujourd'hui est la composabilité. Vuex 5 n'a pas de modules d'espace de noms qui sont tous accessibles depuis le magasin unique. Chacun de ces modules serait divisé en un magasin complètement séparé. C'est assez simple à gérer pour les composants : ils importent simplement les magasins dont ils ont besoin, les lancent et les utilisent. Mais que se passe-t-il si un magasin veut interagir avec un autre magasin ? Dans la v4, l'espacement de noms complique le tout, vous devez donc utiliser l'espace de noms dans vos appels de commit
et de dispatch
, utiliser rootGetters
et rootState
, puis remonter dans les espaces de noms à partir desquels vous souhaitez accéder aux getters et à l'état. Voici comment cela fonctionne dans Vuex 5 :
// 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' // Import the store you want to interact with export default defineStore({ name: 'counter', // Then `use` the store use () { return { greeter: greeterStore } }, state () { return { count: 0 } }, getters: { greetingCount () { return `${this.greeter.greeting} ${this.count}' // access it from this.greeter } } })
Avec la v5, nous importons le magasin que nous souhaitons utiliser, puis l'enregistrons avec use
et il est maintenant accessible partout dans le magasin, quel que soit le nom de propriété que vous lui avez donné. Les choses sont encore plus simples si vous utilisez la variante de l'API de composition de la définition de magasin :
// store/counter.js import { ref, computed } from 'vue' import { defineStore } from 'vuex' import greeterStore from './greeter' // Import the store you want to interact with export default defineStore('counter', ({use}) => { // `use` is passed in to function const greeter = use(greeterStore) // use `use` and now you have full access const count = 0 const greetingCount = computed(() => { return `${greeter.greeting} ${this.count}` // access it like any other variable }) return { count, greetingCount } })
Plus de modules avec espace de noms. Chaque magasin est séparé et est utilisé séparément. Vous pouvez utiliser use
pour rendre une boutique disponible à l'intérieur d'une autre boutique pour les composer. Dans les deux exemples, use
est fondamentalement le même mécanisme que vuex.store
du précédent et ils garantissent que nous instancions les magasins avec la bonne instance de Vuex.
Prise en charge des scripts dactylographiés
Pour les utilisateurs de TypeScript, l'un des plus grands aspects de Vuex 5 est que la simplification a simplifié l'ajout de types à tout. Les couches d'abstraction que les anciennes versions de Vuex avaient rendues presque impossibles et maintenant, avec Vuex 4, elles ont augmenté notre capacité à utiliser les types, mais il y a encore trop de travail manuel pour obtenir une quantité décente de prise en charge des types, alors que dans la v5 , vous pouvez mettre vos types en ligne, comme vous l'espérez et l'attendez.
Conclusion
Vuex 5 semble être presque exactement ce que j'espérais, et probablement beaucoup d'autres, et je pense qu'il ne peut pas arriver assez tôt. Il simplifie la plupart de Vuex, en supprimant une partie de la surcharge mentale impliquée, et ne devient plus compliqué ou verbeux que là où il ajoute de la flexibilité. Laissez des commentaires ci-dessous sur ce que vous pensez de ces changements et quels changements vous pourriez apporter à la place ou en plus. Ou allez directement à la source et ajoutez un RFC (Request for Comments) à la liste pour voir ce que pense l'équipe principale.