Vue 3 mit TypeScript

Table of Content

Why Typescript

Typescript is an open-source language which builds on Javascript by adding static type definitions.

Javascript by default is what is typically called a dynamically type language. Which means that the variables can be assigned any type of value

e.g.

var mysteryBox = "Mystery!" || [] || 6

Typescript can be added incrementally to a project.

How to Setup Typescript

npm install -g @ vue/cli

# OR

yarn global add @vue/cli

Normale Installation von Vue3 mit Typescript durchführen
class-style-syntax NO
Babel YES

tsconfig.json

  • paths
    • "@/*" ⇒ Beschreibt wo Typescript nachschauen soll wenn eine Zeile mit "@" beginnt
  • include
    • Zeigt TypeScript welche Dateien benötigt werden um die Datei zu kompilen

Adding Typescript to an existing Vue 3 Project

vue add typescript
# class-style-syntax NO
# Babel YES
# Convert all .js to .ts YES
# Allow .js files to be compiled: NO
# Skip type checking of all declaration files YES

Adding Typescript to an existing Component

<script lang="ts">

Füge zum Script-Tag das Attribut lang mit dem Wert ts hinzu

import { defineComponent } from 'vue'

Import the defineComponent onto the top to the functional part of your Component

export default defineComponent ({ 
...
})
// Wird zur Funktion! Runde Klammern nicht vergessen!!!

füge defineComponent zum Modul hinzu

Common JavaScript-Types

  • String
  • Number
  • Boolean
  • Array
  • Function
  • Object

Additional Types provided from TypeScript

  • Any – deaktiviert das type checking

  • Tupel – vordefinierte Länge eines Arrays mit vordefinierten Datentypen

    • Beispiel
    // Example: RGB colors in an array
    [number, number, number]
  • Enum – Aufzählungstypen deren Aufzählung optional Werte zugewiesen werden können.

    • Beispiel
    enum ArrowKeys {
        Up=1,
        Down, // = 2
        Left, // = 3
        Right // = 4
    }

Definieren eines Typs für eine Variable

String, Number, Boolean

let stageName: string = 'A Beatufil Vue'
let roomSize: number = 100
let isComplete: boolean = false

Convention: alle Typen werden bei der Zuweisung klein geschrieben

Array

let shoppingList :string[] = ['apple', 'bananas', 'cherries']

Functions

Hier müssen zwei Werte beachtet werden. Zum einen die eigentlichen Parameter und was die Funktion als Wert zurück gibt.

let generateFullName = (firstName: string, lastName: string): string => {
    return firstname + ' ' + lastName; 
}

Objects

Wie werden Typen auf Objekte angewendet?

// Ausgangsobjekt
let person = {
    name: 'Peter Parker',
    age: 20,
    activeAvenger: true,
    powers: ['wall-crawl', 'spider-sense']
}

// Objekt mit Typenzuweisung
let person: {
    name: string;
    age: number;
    activeAvenger: boolean;
    powers: string[];
} = {
    name: 'Peter Parker',
    age: 20,
    activeAvenger: true,
    powers: ['wall-crawl', 'spider-sense']
}

Custom Types

let buttonStyles: string = 'primary'

Image of Basic Buttons

Typen

Typen / Types / Type ist ein Alias der beschreibt wie die Daten aussehen sollen bzw. wie ein Objekt strukturiert werden soll.

type buttonType = 'primary'
type buttonStyles: buttonType = 'primary'  // WORKS

type buttonStyles: buttonType = 'secondary' // WONT WORK weil die Bescheibung
                                                                                    // an dieser Stelle nicht übereinstimmt.
// Definiert war, dass der Typ nur primary als Wert entgegenehmen darf. 
// Hier wurde jedoch secondary übergeben.

Union Operator

Um mehrere Werte hinter einem Typ zu speichern, benötigt man den sog. Union Operator . Dieser funktioniert intern, wie ein logisches Oder (||). In unserem aktuellen Beispiel würde das in Code in etwa so aussehen.

 if (buttonType === 'primary' || buttonType === 'secondary') {//execute}

Bei der Erstellung des Types wird der Union Operator | wie folgt angewendet. Im kommenden Beispiel kann eine falsche Klassenzuweisung verhindert werden.

type buttonType = 'primary' | 'secondary' | 'success' | 'danger'

const errorBtnStlyes: buttonType = 'error'  // WONT WORK!
const errorBtnStlyes: buttonType = 'danger'  // WORKS!

Interface

Ist im Grunde genommen das Type nur für Objekte. Der Bauplan für ein Objekt.

// Auszug aus vorherigem Kapitel
let person: {
    name: string;
    age: number;
    activeAvenger: boolean;
    powers: string[];
} = {
    name: 'Peter Parker',
    age: 20,
    activeAvenger: true,
    powers: ['wall-crawl', 'spider-sense']
}

// Use-case Interace - Generischer Umbau
interface Hero = {
    name: string;
    age: number;
    activeAvenger: boolean;
    powers: string[];
}

let person: Hero = {
    name: 'Peter Parker',
    age: 20,
    activeAvenger: true,
    powers: ['wall-crawl', 'spider-sense']
}

Können Typen mit Union Operatoren mit Interfaces kombiniert werden? – JA

type comicUniverse = 'Marvel' | 'DC'
interface Hero = {
    name: string;
    age: number;
    activeAvenger: boolean;
    powers: string[];
    universe: comicUniverse;
}

let person: Hero = {
    name: 'Peter Parker',
    age: 20,
    activeAvenger: true,
    powers: ['wall-crawl', 'spider-sense'],
    universe: 'Marvel'
}

Data with Custom Types


Extension VueDX: Plugin for advanced TypeScript/JavaScript support for Vue

// EventDetails.vue
<template>
  <div v-if="event">
    <h1>{{ event.title }}</h1>
    <p>{{ event.time }} on {{ event.date }} @ {{ event.location }}</p>
    <p>{{ event.description }}</p>
  </div>
</template>
// EventDetails.vue
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'EventDetails',
  data() {
    return {
      event: null
    }
  }
})

Problematik: Unser Editor/IDE ist nicht in der Lage festzustellen welche Attribute valide sind und welche nicht. Um dies uns sichtbar zu machen, können wir im Zusammenspiel mit [VueDX]() und einem Interface prüfen, ob die Daten & Attribute valide sind. Hierzu schauen wir uns das Objekt event im obigen Beispiel genauer an.

Aktuell ist es ohne weiteres Möglich statt event.title das Attribut event.heading zu verwenden, obwohl dieses nicht existiert. Weder die IDE/ der Editor, noch uns selbst würde dieser Fehler auffallen. Um das zu verhindern können wir dem event ein Interace zuweisen um die Struktur vorzugeben.

Hierfür legen wir im src-Ordner eine neue Datei an. Diese nennen wir types.ts. In dieser Datei definieren wir ein Interface.

// types.ts
interface eventItem {
    id: 123,
    category: string,
    title: string,
    description: string 
    location: string,
    date: string,
    time: string,
    organizer: string
}

und importieren diese im Anschluss in unsere Komponente

import { eventItem } from '../types'

Nun können wir mit der Zuweisung des Inferfaces zum Objekt beginnen.

Type Assertion

export default defineComponent({
  name: 'EventDetails',
  data() {
    return {
      event: null
    }
  },
})

Wenn wir das Interace nun zuweisen wollen, stellen wir fest das dieses Leer ist und nur über einen API-Call befüllt wird. Weisen wir nun das Interface zu, wird TypeScript sich daran stören, weil keine Daten in dem Objekt stehen, obwohl der "Bauplan" etwas anderes vorsieht.

An dieser Stelle müssen wir eingreifen und dem Compiler erklären, dass wir mehr über den Typ wissen als der Compiler selbst.

interface TodoItem {
    label: string,
    completed: boolean
}

const futureTodoItem = {}
futureTodoItem.label = 'Install VueDX extension'
futureTodoItem.completed = true

// TypeScript mag das nicht! Es geht davon aus das futureTodoItem
// ein leeres Objekt ist

Kurzes Beispiel zur Verdeutlichung!

Um an dieser Stelle den TypeScript und den Compiler zu überschreiben, machen wir uns Type Assertions zu nutzen.

Mit as können wir das Standardverhalten überschreiben und unseren Type dem Objekt zuweisen.

interface TodoItem {
    label: string,
    completed: boolean
}

const futureTodoItem = {} as TodoItem
futureTodoItem.label = 'Install VueDX extension'
futureTodoItem.completed = true

// TypeScript mag das nicht! Es geht davon aus das futureTodoItem
// ein leeres Objekt ist

In unserem Vue-Projekt sieht das dann wie folgt aus:

import { eventItem } from '../types'

export default defineComponent({
  name: 'EventDetails',
  data() {
    return {
      event: {} as eventItem
    }
  },
})

Props with Types

Zuweisung von Props soll man die Objektschreibweise verwenden

Beispiel:

// DONT
export default defineComponent({
  props: ['id']
})
//DO INSTEAD
export default defineComponent({
  props: {
    id: {
      type: Number,
      required: true
    }
  },
})

What is generic?

Um Funktionen möglichst wiederverwendbar zu halten, kann man das Konzept von Generics nutzen. Diese sind wie eine Art als Platzhalter für Typen. Hiermit können Funktionen möglichst "allgemein" gehalten werden.

Beispiel:

function createList(item: number): number[] {
    const newList: number[] = []

    newList.push(item)

    return newList
}

const numberList = createList(123)

Diese Funktion ist sehr limitiert. Sie dient ausschließlich dem Zweck Nummern in ein neues Array zu packen und dieses dann zurückzugeben. Man könne die Funktion nun in addNumberToNumberList umbenennen, aber das wäre nicht Zielführend und würde Redundanzen verursachen.

Mit Generics würde der Code wie folgt aussehen

function createList<CustomType>(item: CustomType): CustomType[] {
    const newList: CustomType[] = []

    newList.push(item)

    return newList
}

const numberList = createList<number>(123)

In der TypeScript-Community hat sich die Convention einzelne Buchstaben als Variable zu verwenden durchgesetzt.

function createList<T>(item: T): T[] {
    const newList: T[] = []

    newList.push(item)

    return newList
}

const stringList = createList<number>(123)

In Vue

… nutzt man Generics wie folgt.

  • Import der Methode PropType
import { defineComponent, PropType } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  props: {
    event: {
      type: Object as PropType<EventItem>,
      required: true
    }
  },
})

Custom Types with Computed Properties

<script lang="ts">
import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  data() {
    return {
      events: [] as EventItem
    }
  },
  methods: {
    secondEvent(): EventItem {
      return this.events[1]
    }
  }
})
</script>

For computed properties, focus on what type is returned

Custom Types with Methods

<script lang="ts">
import { defineComponent } from 'vue'
import { EventItem } from '../types'

export default defineComponent({
  data() {
    return {
      events: [] as EventItem[]
    }
  },
  methods: {
    addEvent(newEvent) {
      this.events.push(newEvent)
    }
  }
})
</script>

// Adding Custom Types to the Parameter of a Method
addEvent(newEvent: EventItem) {
  this.events.push(newEvent)
}

// Adding Custom Types to the Return Value of a Method
secondEvent(): EventItem {
  return this.events[1]
}

When it comes to adding custom types to methods, there are two key things to keep in mind:

  • Do we need to add types to the parameters being passed into the method?
  • Do we need to add types to whatever is being returned by the method?

For methods, add types for the parameters and return value if applicable

Weiter Quellen

https://v3.vuejs.org/guide/typescript-support.html#typescript-support

https://www.typescriptlang.org/docs/