Was kommt zu VueX?
Veröffentlicht: 2022-03-10Vuex ist die Lösung für die Zustandsverwaltung in Vue-Anwendungen. Die nächste Version – Vuex 4 – durchläuft die letzten Schritte, bevor sie offiziell veröffentlicht wird. Diese Version bringt volle Kompatibilität mit Vue 3, fügt aber keine neuen Funktionen hinzu. Während Vuex schon immer eine leistungsstarke Lösung und für viele Entwickler die erste Wahl für die Zustandsverwaltung in Vue war, hatten einige Entwickler gehofft, dass mehr Workflow-Probleme behoben werden. Doch selbst als Vuex 4 gerade herauskommt, spricht Kia King Ishii (ein Mitglied des Kernteams von Vue) über seine Pläne für Vuex 5, und ich bin so begeistert von dem, was ich gesehen habe, dass ich es mit Ihnen teilen musste alle. Beachten Sie, dass die Vuex 5-Pläne noch nicht abgeschlossen sind, daher können sich einige Dinge ändern, bevor Vuex 5 veröffentlicht wird, aber wenn es am Ende weitgehend dem ähnelt, was Sie in diesem Artikel sehen, sollte es eine große Verbesserung für die Entwicklererfahrung sein.
Mit dem Aufkommen von Vue 3 und seiner Kompositions-API haben die Leute nach handgefertigten einfachen Alternativen gesucht. Beispielsweise zeigt You Might Not Need Vuex ein relativ einfaches, aber flexibles und robustes Muster für die Verwendung der Kompositions-API zusammen mit provide/inject
zum Erstellen gemeinsam genutzter Zustandsspeicher. Wie Gabor in seinem Artikel feststellt, sollte dies (und andere Alternativen) jedoch nur in kleineren Anwendungen verwendet werden, da ihnen all die Dinge fehlen, die sich nicht direkt auf den Code beziehen: Community-Unterstützung, Dokumentation, Konventionen, gute Nuxt-Integrationen und Entwickler Werkzeuge.
Letzteres war für mich immer eines der größten Probleme. Die Browsererweiterung Vue devtools war schon immer ein erstaunliches Tool zum Debuggen und Entwickeln von Vue-Apps, und der Verlust des Vuex-Inspektors durch „Zeitreisen“ wäre ein ziemlich großer Verlust für das Debuggen von nicht trivialen Anwendungen.
Zum Glück können wir mit Vuex 5 unseren Kuchen haben und ihn auch essen. Es funktioniert eher wie diese Kompositions-API-Alternativen, behält aber alle Vorteile der Verwendung einer offiziellen Zustandsverwaltungsbibliothek. Schauen wir uns nun an, was sich ändern wird.
Ein Geschäft definieren
Bevor wir irgendetwas mit einem Vuex-Shop machen können, müssen wir einen definieren. In Vuex 4 sieht eine Store-Definition wie folgt aus:
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') } } })
Jeder Speicher besteht aus vier Teilen: state
speichert die Daten, getters
geben Ihnen den berechneten Zustand, mutations
werden verwendet, um den Zustand zu verändern, und actions
sind die Methoden, die von außerhalb des Speichers aufgerufen werden, um alles zu erreichen, was mit dem Speicher zu tun hat. Normalerweise begehen Aktionen nicht nur eine Mutation, wie dieses Beispiel zeigt. Stattdessen werden sie verwendet, um asynchrone Aufgaben auszuführen, da Mutationen synchron sein müssen oder sie einfach kompliziertere oder mehrstufige Funktionen implementieren. Aktionen können den Staat auch nicht alleine mutieren; Sie müssen einen Mutator verwenden. Wie sieht also Vuex 5 aus?
import { defineStore } from 'vuex' export const counterStore = defineStore({ name: 'counter', state() { return { count: 0 } }, getters: { double () { return this.count * 2 } }, actions: { increment () { this.count++ } } })
Hier gibt es einige Änderungen zu beachten. Zuerst verwenden wir statt createStore
defineStore
. Dieser Unterschied ist vernachlässigbar, hat aber semantische Gründe, auf die wir später noch eingehen werden. Als nächstes müssen wir einen name
für den Shop angeben, den wir vorher nicht brauchten. In der Vergangenheit bekamen Module ihren eigenen Namen, aber sie wurden nicht vom Modul selbst bereitgestellt; Sie waren nur der Eigenschaftsname, dem sie vom übergeordneten Geschäft zugewiesen wurden, das sie hinzugefügt hat. Jetzt gibt es keine Module . Stattdessen wird jedes Modul ein separater Speicher sein und einen Namen haben. Dieser Name wird von der Vuex-Registrierung verwendet, über die wir später sprechen werden.
Danach müssen wir state
zu einer Funktion machen, die den Anfangszustand zurückgibt, anstatt ihn nur auf den Anfangszustand zu setzen. Dies ähnelt der data
für Komponenten. Wir schreiben getters
sehr ähnlich wie in Vuex 4, aber anstatt den state
als Parameter für jeden Getter zu verwenden, können Sie this
einfach verwenden, um zum Status zu gelangen. Auf die gleiche Weise müssen sich actions
nicht um die Übergabe eines context
kümmern: Sie können this
einfach verwenden, um auf alles zuzugreifen. Schließlich gibt es keine mutations
. Stattdessen werden Mutationen mit actions
kombiniert. Kia bemerkte, dass Mutationen zu oft zu einfachen Settern wurden, was sie sinnlos wortreich machte, also entfernten sie sie. Er erwähnte nicht, ob es „in Ordnung“ sei, den Zustand direkt von außerhalb des Ladens zu mutieren, aber wir sind definitiv erlaubt und ermutigt, den Zustand direkt aus einer Aktion heraus zu mutieren, und das Flux-Muster missbilligt die direkte Mutation des Zustands.
Hinweis : Für diejenigen, die die Kompositions-API der Options-API zum Erstellen von Komponenten vorziehen, wird es Sie freuen zu erfahren, dass es auch eine Möglichkeit gibt, Geschäfte ähnlich wie bei der Verwendung der Kompositions-API zu erstellen.
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 } })
Wie oben gezeigt, wird der Name als erstes Argument für defineStore
. Der Rest sieht aus wie eine Kompositionsfunktion für Komponenten. Dies führt zu genau demselben Ergebnis wie das vorherige Beispiel, bei dem die Options-API verwendet wurde.
Den Shop instanziieren lassen
In Vuex 4 haben sich die Dinge gegenüber Vuex 3 geändert, aber ich werde mir nur v4 ansehen, um zu verhindern, dass die Dinge außer Kontrolle geraten. Als Sie in v4 createStore
aufgerufen haben, haben Sie es bereits instanziiert. Sie können es dann einfach in Ihrer App verwenden, entweder über app.use
oder direkt:
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
Dies ist eine Sache, die Vuex 5 etwas komplizierter macht als in v4. Jede App kann jetzt eine separate Instanz von Vuex erhalten, wodurch sichergestellt wird, dass jede App separate Instanzen derselben Stores haben kann, ohne Daten zwischen ihnen auszutauschen. Sie können eine Instanz von Vuex freigeben, wenn Sie Instanzen von Stores zwischen Apps teilen möchten.
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')
Jetzt haben alle Ihre Komponenten Zugriff auf die Vuex-Instanz. Anstatt Ihre Speicherdefinition direkt anzugeben, importieren Sie sie dann in die Komponenten, in denen Sie sie verwenden möchten, und verwenden die Vuex-Instanz, um sie zu instanziieren und zu registrieren:
import { defineComponent } from 'vue' import store from './store' export default defineComponent({ name: 'App', computed: { counter () { return this.$vuex.store(store) } } })
Durch Aufrufen $vuex.store
wird der Store in der Vuex-Instanz instanziiert und registriert. Von diesem Zeitpunkt an erhalten Sie jedes Mal, wenn Sie $vuex.store
für diesen Store verwenden, den bereits instanziierten Store zurück, anstatt ihn erneut zu instanziieren. Sie können die store
Methode direkt für eine Instanz von Vuex aufrufen, die von createVuex()
erstellt wurde.
Jetzt ist Ihr Geschäft auf dieser Komponente über this.counter
zugänglich. Wenn Sie die Kompositions-API für Ihre Komponente verwenden, können Sie useStore
anstelle von 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 } } })
Es gibt Vor- und Nachteile, den Store direkt in die Komponente zu importieren und dort zu instanziieren. Es ermöglicht Ihnen, Code aufzuteilen und den Speicher nur dort zu laden, wo er benötigt wird, aber jetzt ist es eine direkte Abhängigkeit, anstatt von einem übergeordneten Element injiziert zu werden (ganz zu schweigen davon, dass Sie es jedes Mal importieren müssen, wenn Sie es verwenden möchten). Wenn Sie Dependency Injection verwenden möchten, um es in der gesamten App bereitzustellen, insbesondere wenn Sie wissen, dass es im Stammverzeichnis der App verwendet wird, wo Code-Splitting nicht hilft, können Sie einfach provide
verwenden:
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')
Und Sie können es einfach in jede Komponente einfügen, in der Sie es verwenden werden:
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 } } })
Ich bin nicht begeistert von dieser zusätzlichen Ausführlichkeit, aber sie ist expliziter und flexibler, wovon ich ein Fan bin. Diese Art von Code wird in der Regel gleich zu Beginn des Projekts einmal geschrieben und stört Sie dann nicht mehr, obwohl Sie jetzt entweder jeden neuen Store bereitstellen oder ihn jedes Mal importieren müssen, wenn Sie ihn verwenden möchten, aber Das Importieren oder Einfügen von Codemodulen ist die Art und Weise, wie wir im Allgemeinen mit allem anderen arbeiten müssen, also lässt es Vuex nur mehr so arbeiten, wie die Menschen bereits arbeiten.
Verwenden eines Speichers
Abgesehen davon, dass ich ein Fan der Flexibilität und der neuen Art bin, Stores auf die gleiche Weise zu definieren wie eine Komponente, die die Kompositions-API verwendet, gibt es eine weitere Sache, die mich mehr als alles andere begeistert: wie Stores verwendet werden. So sieht es aus, einen Store in Vuex 4 zu verwenden.
store.state.count // Access State store.getters.double // Access Getters store.commit('increment') // Mutate State store.dispatch('increment') // Run Actions
State
, getters
, mutations
und actions
werden alle auf unterschiedliche Weise über unterschiedliche Eigenschaften oder Methoden gehandhabt. Das hat den Vorteil der Eindeutigkeit, die ich vorhin gelobt habe, aber diese Eindeutigkeit bringt uns nicht wirklich etwas. Und diese API wird nur schwieriger zu verwenden, wenn Sie Namespace-Module verwenden. Im Vergleich dazu scheint Vuex 5 genau so zu funktionieren, wie Sie es normalerweise hoffen würden:
store.count // Access State store.double // Access Getters (transparent) store.increment() // Run actions // No Mutators
Alles – der Zustand, Getter und Aktionen – ist direkt im Stammverzeichnis des Speichers verfügbar, was die Verwendung mit viel weniger Ausführlichkeit vereinfacht und praktisch die Verwendung von mapState
, mapGetters
, mapActions
und mapMutations
für die Options-API oder zum Schreiben überflüssig macht extra computed
Anweisungen oder einfache Funktionen für die Kompositions-API. Dadurch sieht und verhält sich ein Vuex-Shop einfach wie ein normaler Shop, den Sie selbst erstellen würden, aber er bietet alle Vorteile von Plugins, Debugging-Tools, offizieller Dokumentation usw.
Komponieren von Geschäften
Der letzte Aspekt von Vuex 5, den wir uns heute ansehen werden, ist die Zusammensetzbarkeit. Vuex 5 verfügt nicht über Namespace-Module, auf die alle über den einzelnen Store zugegriffen werden kann. Jedes dieser Module würde in einen völlig separaten Speicher aufgeteilt. Das ist für Komponenten einfach genug: Sie importieren einfach die Stores, die sie benötigen, und feuern sie an und verwenden sie. Was aber, wenn ein Geschäft mit einem anderen Geschäft interagieren möchte? In v4 verwickelt der Namespace das Ganze, also müssen Sie den Namespace in Ihren commit
und dispatch
-Aufrufen verwenden, rootGetters
und rootState
und sich dann in die Namespaces hocharbeiten, von denen Sie auf Getter und Status zugreifen möchten. So funktioniert es in 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 } } })
Mit v5 importieren wir den Store, den wir verwenden möchten, registrieren ihn dann bei use
und können jetzt im gesamten Store unter dem von Ihnen angegebenen Eigenschaftsnamen darauf zugreifen. Die Dinge sind noch einfacher, wenn Sie die Kompositions-API-Variante der Store-Definition verwenden:
// 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 } })
Keine Namespace-Module mehr. Jeder Speicher ist separat und wird separat verwendet. Sie können use use
, um ein Geschäft in einem anderen Geschäft verfügbar zu machen, um sie zu erstellen. In beiden Beispielen use
im Grunde derselbe Mechanismus wie bei vuex.store
von früher verwendet, und sie stellen sicher, dass wir die Stores mit der richtigen Instanz von Vuex instanziieren.
TypeScript-Unterstützung
Für TypeScript-Benutzer ist einer der größten Aspekte von Vuex 5, dass die Vereinfachung das Hinzufügen von Typen zu allem vereinfacht. Die Abstraktionsschichten, die ältere Versionen von Vuex hatten, machten es fast unmöglich, und jetzt, mit Vuex 4, haben sie unsere Fähigkeit zur Verwendung von Typen verbessert, aber es gibt immer noch zu viel manuelle Arbeit, um eine angemessene Menge an Typunterstützung zu erhalten, während in v5 , können Sie Ihre Typen inline setzen, so wie Sie es hoffen und erwarten würden.
Fazit
Vuex 5 scheint fast genau das zu sein, was ich – und wahrscheinlich viele andere – erhofft hatten, und ich denke, es kann nicht früh genug kommen. Es vereinfacht den größten Teil von Vuex, beseitigt einen Teil des damit verbundenen mentalen Overheads und wird nur komplizierter oder ausführlicher, wenn es Flexibilität hinzufügt. Hinterlassen Sie unten Kommentare, was Sie von diesen Änderungen halten und welche Änderungen Sie stattdessen oder zusätzlich vornehmen könnten. Oder gehen Sie direkt zur Quelle und fügen Sie der Liste einen RFC (Request for Comments) hinzu, um zu sehen, was das Kernteam denkt.