Das Javascript-Framework Vue.js, Teil 9 State-Management mit Vuex

Von Stephan Augsten

Vue.js-Komponenten können miteinander kommunizieren, um sich bei Datenänderungen auf den aktuellen Stand zu bringen. Vuex bildet dieses State-Management ab, wir stellen die Bibliothek näher vor.

Anlegen eines Projekts mit dem Vuex-Feature.
Anlegen eines Projekts mit dem Vuex-Feature.
(Bild: Dr. Koller)

In Vue.js übergeben Parent- ihren Child-Komponenten Informationen über den Props-Mechanismus. Die Child-Komponenten wiederum senden Custom Events, die von den Parent-Komponenten aufgefangen werden können. Diese Techniken haben wir in Teil 5 der Vue.js-Reihe bereits vorgestellt.

In größeren Anwendungen mit vielen Komponenten kommt dieses Hin- und Herschieben von Daten jedoch an Grenzen. Ein zentraler Datenbestand, der von allen Komponenten genutzt wird, ist dann oft sinnvoller. Es gibt für Vue.js mehrere Bibliotheken, in denen State Management-Patterns umgesetzt wurden. Die bekannteste Lösung nennt sich Vuex.

Installation

Anlegen eines Projekts mit Vuex-Feature.
Anlegen eines Projekts mit Vuex-Feature.
(Bild: Dr. Koller / Microsoft)

Beim Anlegen eines neuen Projekts mit dem CLI lässt sich Vuex einfach als Feature unter „Manually select features“ anhaken. Das nachträgliche Zufügen von Vuex zu einer bestehenden (hier Vue-2-)Anwendung ist etwas aufwendiger, aber auch kein Problem. Man installiert dazu im Projekt die Abhängigkeit vuex:

npm install vuex@3.6.2 –save

Anschließend wird die Datei „store/index.js“ mit folgendem Inhalt angelegt:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },'
  actions: {
  },
  modules: {
  }
})

Der Vuex-Store ist ein Container, der den Zustand der Anwendung aufbewahrt. Er wird in main.js als Plug-in eingebunden:

import store from './store'
...
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

Vuex ist damit einsatzbereit.

Konzepte

Die Konzepte von Vuex lassen sich anhand der Keys im oben aufgeführten Store beschreiben. Unter State werden die eigentlichen Daten hinterlegt. Der lesende Zugriff auf diese Daten erfolgt mithilfe von Gettern. Geschrieben wird dagegen mithilfe von Mutations. Soll das Schreiben asynchron erfolgen, müssen dafür Actions programmiert werden.

Das Ganze wird anhand eines Beispiels schnell deutlich. Hier wird State ein Objekt namens message zugefügt:

export default new Vuex.Store({
  state: {
    message: 'Hello From Vuex'
  },
});

Mithilfe von Computed Properties können nun Komponenten auf den State, und damit message, zugreifen:

...
computed: {
  message() {
    return this.$store.state.message;
  }
}

Getter

Diese Vorgehensweise hat allerdings den Nachteil, das Änderungen vor Rückgabe des Werts in den Computed Properties jeder Komponente erneut ausprogrammiert werden müssen. Eleganter und richtiger ist es deshalb, im Store einen Getter für message anzulegen. Im Beispiel wird der Wert in Großbuchstaben umgewandelt, um anzudeuten, dass der Wert vor der Rückgabe in irgendeiner Form manipuliert werden könnte:

export default new Vuex.Store({
  state: {
    message: 'Hello From Vuex'
  },
  getters: {
    message(state) {
      return state.message.toUpperCase();
    }
  },
  ...

Der Zugriff in den Komponenten erfolgt dann über diesen Getter und nicht mehr direkt über den State:

computed: {
  message() {
    return this.$store.getters.message;
  }

Mutations

Dem Pattern hinter Vuex liegt die Idee zugrunde, dass der State des Store aus Gründen der besseren Nachvollziehbarkeit (zum Beispiel für Logging oder IDE-Unterstützung) nicht direkt geändert werden soll. Zum Schreiben von Daten werden deshalb Mutationen angelegt. Das sind Funktionen, die unter dem Schlüssel mutations im Store aufgeführt werden. Mutationen können Daten, also Parameter, in Form eines Payloads übergeben werden:

mutations: {
  appendText(state, payload) {
    state.message = state.message + payload;
  }
},

Ausgeführt werden die Mutationen in der Komponente mit store.commit(). Hier wird einfach ein Ausrufezeichen als Payload übergeben und somit an den Text in message angehängt:

methods: {
  buttonKlicked() {
    this.$store.commit("appendText", "!");
  },
},

Actions

Soll das Schreiben asynchron erfolgen, ist dafür eine Action unter gleichnamigen Schlüssel anzulegen. Innerhalb der Action kann dann die Mutation aufgerufen werden (zum Beispiel nach dem Aufruf eines Webservice der die Daten liefert):

actions: {
  async appendText(state, payload) {
    // hier könnte Ihr Service-Aufruf stehen
    state.commit('appendText', payload);
  }
}

Das Ausführen von Aktionen erfolgt mit der Funktion dispatch():

this.$store.dispatch('appendText', "!");

Vuex-Helpers: mapGetters und mapActions

Ein Aufruf wie this.$store.dispatch(‘appendText’, “!”) ist recht unleserlich. Wäre es nicht eleganter, wenn man stattdessen einfach this.appendText(“!”) formulieren könnte? Kein Problem, genau dafür wurden die Vuex-Helper entwickelt, die den Komponenten entsprechende Funktionen hinzufügen:

import { mapActions } from "vuex";
...
methods: {
  ...mapActions(['appendText'])
}

Auch für die Getter steht ein Helper zur Verfügung. mapGetters() generiert die Get-Methode und wird in computed angewandt:

import { mapGetters } from "vuex";
...
computed: {
  ...mapGetters(['message']),
}

Die einfache Komponente, die den Store hier nutzt, sieht schließlich folgendermaßen aus:

<template>
  <div>
    {{ message }}
    <button @click="buttonKlicked">Text ändern</button>
  </div>
</template>
<script>
import { mapActions } from "vuex";
import { mapGetters } from "vuex";
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  methods: {
    buttonKlicked() {
      this.appendText("!");
    },
    ...mapActions(["appendText"]),
  },
  computed: {
    ...mapGetters(["message"]),
  },
};
</script>

Die grundlegenden Funktionen von Vuex sind damit vorgestellt. Es bleibt zu erwähnen, dass nicht alle Daten in den Store gepackt werden müssen. Lokale Daten, die nur innerhalb einer Komponente verwendet werden, sind dort nach wie vor am besten aufgehoben.

(ID:48188488)

Jetzt Newsletter abonnieren

Täglich die wichtigsten Infos zu Softwareentwicklung und DevOps

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung