JavaScript: Modernes State Management ohne zusätzliche Library in Vue.js 3

Seite 2: Vorteile der Composition API

Inhaltsverzeichnis

Zum besseren Verständnis der Nützlichkeit der Composition API, beschreiben die folgenden Absätze die Eigenschaften von Vue.js 2, die vor allem bei größeren Applikationen schwierig zu pflegen wurden.

1. Objektstruktur: Je größer eine Single-File Component wird, desto unübersichtlicher wird sie. Im gewissen Maß lässt sich eine zu groß gewordene Komponente beim Refactoring verkleinern, in dem sie in mehrere kleine Komponenten aufgeteilt wird. Jede Komponente sollte dem Single-Responsibility-Prinzip des SOLID Designs folgen. Dennoch, sobald die Komplexität einer Applikation wächst, wird sich auch die Anzahl benötigter Codezeilen steigern. Das ist oft bei Elternkomponenten der Fall, die auf unterschiedliche Werte ihrer Kind-Komponenten im Zusammenhang reagieren.

Man denke sich nun den Webshop hinein. Auf der Startseite ist eine Komponente eingebunden. Diese kümmert sich darum, die Nutzer zu begrüßen und die Artikel des Warenkorbes anzuzeigen. Die Nutzer können Produkte in der Ansicht auch wieder löschen (Listing 4):

<template>
  <section>
    <p>{{ greeting }} {{ fullName }}!</p>
    <p>These are your items:</p>
    <ul class="items">
      <li v-for="item in items" :key="item.id">
        <div>
          {{ item.count }}x <strong>{{ item.name }}</strong>
        </div>
        <button @click="deleteItem(item.id)">delete</button>
      </li>
    </ul>
  </section>
</template>

<script>
export default {
  data: () => {
    return {
      greeting: "Hello",
      items: [
        {
          id: 0,
          name: "Eggs",
          count: 10,
        }
      ],
    };
  },
  props: {
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
  },
  computed: {
    fullName: function () {
      return `${this.firstName} ${this.lastName}`;
    },
  },
  methods: {
    deleteItem: function (id) {
      this.items = this.items.filter((item) => item.id !== id);
    },
  },
};
</script>

Obwohl die Produkte items, die Methode zum Löschen von Produkten deleteItem[code] und die Begrüßung [code]greeting mit dem Computed Property des vollen Namens fullName logisch betrachtet nicht zusammengehören, sind sie über die Objektstruktur der Vue.js-Instanz verteilt. Denn aufgeteilt wird nach den Rubriken von data[code], [code]props, computed und methods. Je größer die Komponente, desto schwieriger ist es zu überblicken, welche Teile logisch zusammengehören.

Abbildung 1 zeigt eine abstrakte Darstellung einer Komponente. Die verschiedenen bunten Bereiche stellen zusammengehörende logische Einheiten dar, die sich immer wieder selbst unterbrechen.

Große Komponente mit mehreren getrennten Einheiten (Abb. 1)

2. Mixins: Wenn man komplexe Applikationen schreibt und mehrere hunderte Komponenten verwaltet, braucht es eine Option, Code wiederverwendbar zu machen. Vor allem das Teilen von logischem Code, anstelle von vollständigen Komponenten samt Templates wird wichtiger, je größer die Applikation wird.

Mixins sind in Vue.js 2 die Lösung zur Wiederverwendung von Funktionalitäten. Es handelt sich dabei um JavaScript-Dateien, die der Struktur einer Single-File Component folgen. Sie lassen sich mithilfe der Options API in eine Single-File Component einbinden.

Es gibt zwei Nachteile der mixins:

  1. Intransparenz: In größeren Komponenten, zum Beispiel mit mehr als 200 Codezeilen, passiert es schnell, die eine Zeile mixins: [mixin] zu übersehen. Zudem kann ein mixin beliebig viel Funktionalität zu der Instanz der Single-File Component hinzufügen.
  2. Überschreiben/Überladen: In Listing 1 gibt es jeweils den Lifecycle Hook created und die Methode addItem. Es ist auf den ersten Blick nicht ersichtlich, ob sich die jeweiligen Methoden jeweils überschreiben oder überladen. Wenn beide ausgeführt werden, ist es nicht offensichtlich, in welcher Reihenfolge dies geschehen wird.

Im Laufe der Zeit haben sich die genannten Nachteile von Mixins als zu groß erwiesen.

3. Workarounds für nicht-reaktive Daten: Vue.js erleichtert das Erstellen reaktiver Daten immens. Doch in Vue.js 2 fühlte sich das Erstellen von nicht-reaktiven Daten jedes Mal wie ein Workaround an. Legte man eine Konstante im data-Block an, war sie automatisch reaktiv, obwohl sie sich nie ändern würde. Alternativ konnten Konstanten im Lifecycle Hook created initialisiert werden, was sich ebenfalls nach zu viel Code für eine einfache Konstante anfühlte.

Die Composition API ermöglicht es Entwicklerinnen und Entwicklern, vollständige Komponenten zu schreiben und dabei ihre logischen Einheiten miteinander zu verbinden – das macht sie zu einem mächtigen Werkzeug. Vorteilhaft ist der Einsatz der Reactivity und Composition API für die Wiederverwendung von Logik. Obwohl sich die Composition API auch sehr gut für nicht-wiederverwendete Einheiten eignet, ist dies oft das Paradebeispiel, um die Mächtigkeit des Tools zu zeigen.

Die Composition API zeigt ihre Stärke allerdings nicht nur bei großen, komplexen Komponenten mit geteilter Funktionalität. In Listing 5 wird eine Single-File Component von der Options API zur Compositions API migriert.

Zu beachten ist, dass der Code nicht mehr nach Typ wie data, computed und methods gruppiert ist. Stattdessen gibt es zwei logische zusammengehörende Einheiten:

  1. Begrüßung + Name
  2. Produkte
<template>
  <section>
    <p>{{ greeting }} {{ fullName }}!</p>
    <p>These are your items:</p>
    <ul class="items">
      <li v-for="item in items" :key="item.id">
        <div>
          {{ item.count }}x <strong>{{ item.name }}</strong>
        </div>
        <button @click="deleteItem(item.id)">delete</button>
      </li>
    </ul>
  </section>
</template>

<script>
  import { computed, ref } from "vue";
 
  export default {
    name: "HelloWorld",
    props: {
      firstName: {
        type: String,
        required: true,
      },
      lastName: {
        type: String,
        required: true,
      },
    },
    setup(props) {
      /* Logische Einheit Start: Begrüßung + Name */
      const greeting = "Hello"; // Nicht-reaktive Konstante
      const fullName = computed(() => `${props.firstName} 
                                               ${props.lastName}`);
      /* Logische Einheit Ende: Begrüßung + Name */
      
      /* Logische Einheit Start: Produkte */
      const items = ref([
        {
          id: 0,
          name: "Eggs",
          count: 10
        }
      ])
      
      function deleteItem(id) {
        items.value = items.value.filter(item => item.id !== id);
      }
      /* Logische Einheit Ende: Produkte */
      
      return {
        greeting,
        fullName,
        items,
        deleteItem
      }
    }
  }
</script>

Abbildung 2 zeigt die abstrakte Darstellung einer Komponente. Die verschiedenen bunten Bereiche stellen zusammengehörende logische Einheiten dar, die sich dank der Composition API in einer Datei nicht mehr abwechseln.

Große Komponente mit mehreren zusammengehörenden Einheiten (Abb.2 )

Wächst eine Komponente, die ausschließlich mit der Composition API implementiert wurde, fällt es Entwicklern leichter, die verschiedenen logischen Einheiten zu gruppieren. Dennoch wächst sie weiter. Daher stellt sich die Frage, wie sich der gesamte Code noch besser strukturieren lässt. In Listing 5 wurden Kommentare verwendet, um den jeweiligen Start und das Ende einer logischen Einheit zu kennzeichnen. Eine bessere Methode ist das Group, Extract, Share Pattern von Markus Oberlehner.