Webentwicklung: TypeScript in Vue-Projekten

Seite 3: Die verschiedenen API-Modelle

Inhaltsverzeichnis

Anwendungen mit Vue lassen sich auf verschiedene Arten implementieren. Der Quasi-Standard ist die sogenannte Options API. Diese Art der Implementierung von Komponenten stellt die Dokumentation vor. Sie beschreibt den Normalfall, wenn keine abweichenden Einstellungen konfiguriert sind.

Die Options API enthält Sektionen wie den Data-Bereich, ein Template, Lifecycle Hooks und weitere. Hier ergeben sich einige nennenswerte und sinnvolle Änderungen, wenn TypeScript zum Einsatz kommt. Das Exportieren einer Komponente – an dieser Stelle ist eine Single File Component (SFC) im Einsatz – muss anders geschehen. Das zeigt das nachfolgende Listing. Auch die Angabe der Sprache mit lang=“ts“ im script-Tag ist notwendig.

<script lang="ts">
export default Vue.extend({
  name: 'OptionsApiTypeScriptComponent',
  components: {},
  props: {
    msg: String
  },
  data() {
    return {
      count: Number
    }
  },
  computed: {},
  created() {
    console.log('created');
  },
  methods: {},
});
</script>

Angedeutet sind einige Bereiche der Options API wie die Properties, Daten der Komponente, andere Komponenten, ein Lifecycle Hook und der Methodenbereich. An dieser Stelle sind die Eigenschaft msg der Komponenten und die Datenvariable count zu sehen, die typisiert sind, was TypeScript ermöglicht. Die Zeile

export default Vue.extend( {})

aktiviert die Typ-Inferenz von TypeScript, sodass die Annotationen möglich sind. Ein Aufruf von

export default { }

wie bei JavaScript lässt sich nicht einsetzen, wenn Typ-Definitionen zur Verfügung stehen sollen – eine kleine Änderung, die einen großen Mehrwert bietet. Einen Datentyp vom Wert der Variablen ableiten zu lassen, ist ein klarer Vorteil, ebenso wie die Prüfung auf die korrekte Verwendung dieses Datentyps im Programm durch TypeScript.

Falls die Options API nicht zum Einsatz kommen soll und Komponenten als Klassen zu bevorzugen sind, hilft der TypeScript-Dekorator Vue Class Component weiter. Mit dieser Erweiterung lassen sich Klassen mit TypeScript implementieren und durch die Annotationen so ausweisen, dass sich beim Build-Vorgang Vue-Komponenten daraus erstellen lassen. Das kommt der Notation, der Lesbarkeit und der Wartbarkeit zugute. Es ist erforderlich, die Erweiterung als Paket zu installieren. Dann lässt sie sich bei den Vue-Komponenten einbinden.

yarn add vue-class-component

Eine dem vorherigen Beispiel ähnliche Komponente sieht als Class Component wie folgt aus:

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class ClassApiTypeScriptComponent extends Vue {
  @Prop() private msg!: string;

  private count = 0;

  created(): void {
    console.log('created');
  }

  get counter(): number {
    return this.count;
  }

  increment(): void {
    this.count++;
  }
}
</script>

Properties einer Komponente sind mit Prop zu annotieren. Daten einer Komponente können als normale Eigenschaften der Klasse implementiert sein, was die Lesbarkeit des Codes einer Vue-Komponente deutlich erhöht. Das gilt ebenso für Methoden und Lifecycle Hooks, die als reguläre Methoden inklusive TypeScript-Typ-Definitionen implementiert sind. Eine berechnete Eigenschaft, in diesem Fall counter, ist als Getter-Methode definiert. Bei anderen Features einer Vue-Komponente, zum Beispiel Emit oder Watcher, ist das Prinzip ähnlich: Es lassen sich reguläre Methoden definieren, um sie dann zu annotieren und ihnen eine entsprechende Aufgabe zuzuweisen. Das folgende Beispiel zeigt das für einen fiktiven Watcher:

@Watch('name')
nameChanged(newValue: string) {
  this.name = newValue;
}

Insgesamt sind nach diesem Prinzip definierte Vue-Komponenten deutlich stärker an eine Klasse angelehnt, wie es aus objektorientierten Programmiersprachen bekannt ist. Für viele Entwickler macht das den Einstieg in die Welt der Webentwicklung einfacher und kann zusätzlich die Codequalität erhöhen. Die Funktionalität ist dabei identisch zur Options API, so dass die Variante eher vom persönlichen Geschmack oder der Ausrichtung des Projekts abhängt. Wer ein kleines Projekt mit wenigen Komponenten plant, spart sich vielleicht den zusätzlichen Aufwand, der dann eher als Overhead empfunden wird.

Zu guter Letzt ist mit Vue 3 die Composition API hinzugekommen. Sie ermöglicht eine lose(re) Kopplung von Funktionalitäten, sodass Vue-Komponenten endlich wirklich wiederverwendbar sind. Wiederholbare Teile lassen sich besser vom Template, also dem UI, trennen und zu neuen Funktionalitäten in anderen Komponenten zusammensetzen. Die Composition API ist vollständig optional und muss nicht zwingend zum Einsatz kommen, nur weil die Wahl auf Vue 3.x fällt. Ebenfalls optional ist der Einsatz von TypeScript, denn die Composition API funktioniert auch mit JavaScript. Entwicklern bleibt somit für ihr Projekt die freie Wahl. Entscheiden sie sich für TypeScript, bietet das die beschriebenen Vorteile der Typdefinitionen. Daneben existierten auch sekundäre Vorteile bei der Kombination von TypeScript und der Composition API. Die Art und Weise, wie Komponenten und die Komposition ebendieser in der Composition API geregelt sind, weist eine höhere Kompatibilität mit TypeScript auf. Verteilte Funktionen und Objekte, die miteinander zu kombinieren sind, profitieren stark von einem Typsystem und einem Compiler, der dieses prüft. Das betrifft ebenso das Tooling im Editor und wirkt sich auch auf die Nutzung des Reactivity-Systems aus, wenn dem Compiler die dort vorhandenen Datentypen konsequent bekannt sind. Das zeigt das nachfolgende Listing:

<script lang="ts">
import { defineComponent, ref, reactive, onMounted } from 'vue';

interface IPerson {
  name: string
  age: number
}

export default defineComponent({
  name: 'CompositionApiTypeScriptComponent',
  props: {
    msg: String,
    count: Number,
  },

  setup(props) {
    console.log(props.msg);

    let counter = ref(0);
    let year = ref<string | number>('2021');
    let person = reactive<IPerson>({ name: 'Arnold', age: 70 });

    onMounted(() => {
      console.log('created');
    });
    
    return { counter, year, person };
  }
});
</script>

Der Parameter props in der setup-Methode ist nicht separat zu typisieren, denn das geschieht automatisch durch die weiter oben gezeigte Definition der Daten inklusive Datentypen. Reaktive Variablen, die mit ref oder reactive deklariert sind, übernehmen den Datentyp des initialen Werts. In komplexen Szenarien kann es notwendig sein, diese Typisierung zu erweitern, wie die Beispiele der Variablen year und person zeigen. Die generischen Varianten von ref und reactive erlauben es, den Datentyp anzugeben. Über computed() berechnete Werte übernehmen ebenfalls den Datentyp des initialen Wertes. Bei Event-Handlern ist es weiterhin möglich, den Event-Typ zu nutzen, damit TypeScript für den Event-Parameter nicht any annehmen muss:

const onClick = (event: Event) => {
    // ďż˝
}

Die Kombination von Composition API und TypeScript ist durch die Typdefinitionen und die Möglichkeiten der Trennung von logischen Codeblöcken sehr mächtig. Es scheint so, dass eine gute Lösung zum jetzigen Zeitpunkt darin besteht, die Composition API – und nicht die Options API oder die Class API – für Vue-Komponenten zu verwenden. Das hängt weniger davon ab, ob JavaScript oder TypeScript zum Einsatz kommt, allerdings scheint die Composition API der Weg in die Zukunft für Vue mit Typescript zu sein. Die Möglichkeit, Logik von der Präsentation zu trennen, vereinfacht zudem viele Testszenarien bei Unit-Tests, zum Beispiel, weil verschiedene Bereiche einer Komponente, die Geschäftslogik enthalten, in einzelne Klassen aufteilbar sind. Diese lassen sich bei der Composition API sehr einfach zu Funktionalitäten zusammensetzen. Gleichzeitig sind die einzelnen Klassen gut testbar. Das verspricht auch in Zukunft einige Vorteile bei der Implementierung von Vue-Anwendungen.

Die Beispiele haben gezeigt, dass TypeScript in allen drei Varianten bei der Implementierung von Vue-Komponenten realistisch einsetzbar ist und Vorteile verspricht. Daher lässt sich TypeScript bereits in existierenden Projekten nutzen, unabhängig davon, welche Vue-API zum Einsatz kommt. TypeScript macht die spätere Transition zu einer anderen Vue-Version oder zu einem anderen API-Stil nicht schwieriger.