TypeScript mit Vue
TypeScript ist ein typisiertes Superset von JavaScript, das zu JavaScript kompiliert wird. Es bietet statische Typisierung und bessere IDE-Unterstützung.
Vorteile:
- Typsicherheit - Fehler zur Compile-Zeit
- IntelliSense - Bessere Autovervollständigung
- Refactoring - Sichere Code-Änderungen
Wann TypeScript verwenden?
Abschnitt betitelt „Wann TypeScript verwenden?“- Größere Projekte (> 5 Komponenten)
- Team-Entwicklung
- Langlebige Codebasen
- Komplexe Datenstrukturen
- API-Integration
- Wiederverwendbare Libraries
- Prototypen / Experimente
- Sehr kleine Projekte
- Solo-Projekte mit kurzer Lebensdauer
- Wenn Team keine TS-Erfahrung hat
Grundlegende Typen
Abschnitt betitelt „Grundlegende Typen“// Primitive Typenconst name: string = 'Max'const age: number = 30const isActive: boolean = true
// Arraysconst numbers: number[] = [1, 2, 3]const names: Array<string> = ['Max', 'Anna']
// Objekteconst user: { name: string; age: number } = { name: 'Max', age: 30}
// Union Typeslet id: string | number = 123id = 'abc' // Auch erlaubt
// Literal Typestype Status = 'pending' | 'active' | 'done'const status: Status = 'active'Type Inference
Abschnitt betitelt „Type Inference“TypeScript kann Typen oft automatisch ableiten - explizite Annotationen sind dann optional:
// TypeScript erkennt die Typen automatischconst name = 'Max' // stringconst age = 30 // numberconst isActive = true // booleanconst items = [1, 2, 3] // number[]
// Bei Funktionen: Parameter annotieren, Rückgabe wird abgeleitetfunction add(a: number, b: number) { return a + b // Rückgabe: number (automatisch)}
// Manchmal explizit nötigconst user = ref<User | null>(null) // Ohne Annotation wäre es ref<null>Interfaces vs Type Aliases
Abschnitt betitelt „Interfaces vs Type Aliases“interface User { id: number name: string email: string}
// Erweiterbarinterface Admin extends User { permissions: string[]}
// Declaration Merginginterface User { age?: number // Wird hinzugefügt}type User = { id: number name: string email: string}
// Intersectiontype Admin = User & { permissions: string[]}
// Mehr Möglichkeitentype ID = string | numbertype Callback = (data: User) => voidGenerics
Abschnitt betitelt „Generics“Generics ermöglichen wiederverwendbare Typen mit Platzhaltern:
// Generische Funktionfunction identity<T>(value: T): T { return value}identity<string>('hello') // T = stringidentity(42) // T = number (inferiert)
// Generisches Interfaceinterface ApiResponse<T> { data: T status: number message: string}
const userResponse: ApiResponse<User> = { data: { id: 1, name: 'Max', email: 'max@test.de' }, status: 200, message: 'OK'}
// Vue nutzt Generics intensivconst count = ref<number>(0)const users = ref<User[]>([])Vue + TypeScript: Setup
Abschnitt betitelt „Vue + TypeScript: Setup“<!-- TypeScript aktivieren mit lang="ts" --><script setup lang="ts">import { ref, computed } from 'vue'
// Typen werden oft automatisch inferiertconst count = ref(0) // Ref<number>const message = ref('Hello') // Ref<string>
// Explizit wenn nötigconst user = ref<User | null>(null)const items = ref<string[]>([])
// Computed: Rückgabetyp automatischconst doubleCount = computed(() => count.value * 2) // ComputedRef<number></script>Props mit TypeScript
Abschnitt betitelt „Props mit TypeScript“<script setup lang="ts">// Interface für Props definiereninterface Props { title: string count?: number // Optional mit ? items: string[] user: { id: number name: string }}
// defineProps mit Genericconst props = defineProps<Props>()
// Mit Default-Wertenconst props = withDefaults(defineProps<Props>(), { count: 0, items: () => [] // Factory für Arrays/Objekte})
// Zugriff (voll typisiert)console.log(props.title) // stringconsole.log(props.count) // number | undefined (oder number mit default)</script>Emits mit TypeScript
Abschnitt betitelt „Emits mit TypeScript“<script setup lang="ts">// Emits mit Payload-Typenconst emit = defineEmits<{ (e: 'update', value: string): void (e: 'delete', id: number): void (e: 'submit', data: { name: string; email: string }): void}>()
// Alternativ: Kurzschreibweise (Vue 3.3+)const emit = defineEmits<{ update: [value: string] delete: [id: number] submit: [data: { name: string; email: string }]}>()
// Verwendung - Typen werden geprüftemit('update', 'neuer Wert') // OKemit('update', 123) // Fehler: number statt stringemit('unknown') // Fehler: Event existiert nicht</script>ref() und reactive() typisieren
Abschnitt betitelt „ref() und reactive() typisieren“<script setup lang="ts">import { ref, reactive } from 'vue'
// ref() - Generic für komplexe Typeninterface User { id: number name: string}
const user = ref<User | null>(null)const users = ref<User[]>([])
// reactive() - Typ wird meist inferiertconst state = reactive({ count: 0, name: 'Max'})// Typ: { count: number; name: string }
// Explizit wenn nötiginterface State { users: User[] loading: boolean}const state = reactive<State>({ users: [], loading: false})</script>Template Refs typisieren
Abschnitt betitelt „Template Refs typisieren“<script setup lang="ts">import { ref, onMounted } from 'vue'
// DOM-Element typisierenconst inputRef = ref<HTMLInputElement | null>(null)const canvasRef = ref<HTMLCanvasElement | null>(null)
onMounted(() => { // TypeScript weiß jetzt den Element-Typ inputRef.value?.focus() inputRef.value?.select()
const ctx = canvasRef.value?.getContext('2d')})
// Komponenten-Ref typisierenimport MyComponent from './MyComponent.vue'const componentRef = ref<InstanceType<typeof MyComponent> | null>(null)</script>
<template> <input ref="inputRef" /> <canvas ref="canvasRef"></canvas> <MyComponent ref="componentRef" /></template>Composables mit TypeScript
Abschnitt betitelt „Composables mit TypeScript“import { ref, type Ref } from 'vue'
interface UseFetchReturn<T> { data: Ref<T | null> error: Ref<Error | null> isLoading: Ref<boolean> execute: () => Promise<void>}
export function useFetch<T>(url: string): UseFetchReturn<T> { const data = ref<T | null>(null) as Ref<T | null> const error = ref<Error | null>(null) const isLoading = ref(false)
async function execute() { isLoading.value = true try { const response = await fetch(url) data.value = await response.json() } catch (e) { error.value = e as Error } finally { isLoading.value = false } }
return { data, error, isLoading, execute }}Options API: Schwierigkeiten
Abschnitt betitelt „Options API: Schwierigkeiten“Die Options API hat fundamentale Probleme mit TypeScript wegen this:
<script lang="ts">import { defineComponent } from 'vue'
// defineComponent() ist PFLICHT für TypeScriptexport default defineComponent({ data() { return { count: 0, user: null as User | null // Casting nötig } }, computed: { // this-Typ funktioniert, aber ist komplex doubleCount(): number { return this.count * 2 } }, methods: { increment() { this.count++ // this muss von defineComponent inferiert werden } }})</script>Probleme im Detail
Abschnitt betitelt „Probleme im Detail“defineComponent()Wrapper nötigthis-Typisierung ist fragil- Data-Properties brauchen Type Assertions
- Mixins sind kaum typisierbar
- Schlechte IDE-Unterstützung in Templates
Composition API: Vorteile für TypeScript
Abschnitt betitelt „Composition API: Vorteile für TypeScript“<script setup lang="ts">// Alles ist explizit und typsicher
// Props - volle Typisierungconst props = defineProps<{ user: User }>()
// Emits - Payload-Typenconst emit = defineEmits<{ update: [value: string] }>()
// State - Inference oder explizitconst count = ref(0)const user = ref<User | null>(null)
// Composables - generische Rückgabetypenconst { data, isLoading } = useFetch<User[]>('/api/users')
// Computed - automatische Typisierungconst userName = computed(() => user.value?.name ?? 'Gast')</script>Keine Wrapper, kein this, volle IDE-Unterstützung, Generics funktionieren
Utility Types
Abschnitt betitelt „Utility Types“TypeScript bietet nützliche eingebaute Utility-Types:
interface User { id: number name: string email: string role: 'admin' | 'user'}
// Alle Properties optionaltype PartialUser = Partial<User>
// Alle Properties requiredtype RequiredUser = Required<User>
// Nur bestimmte Propertiestype UserCredentials = Pick<User, 'email' | 'role'>
// Properties ausschließentype PublicUser = Omit<User, 'email'>
// Readonlytype ReadonlyUser = Readonly<User>
// Record für Key-Value Mapstype UserRoles = Record<string, 'admin' | 'user'>Häufige Fehler und Lösungen
Abschnitt betitelt „Häufige Fehler und Lösungen“// Fehler: Object is possibly nulluser.value.name
// Optional Chaininguser.value?.name
// Non-null Assertion (wenn sicher)user.value!.name
// Type Guardif (user.value) { user.value.name // OK}// Verliert Typsicherheitconst data: any = response
// Typ definiereninterface ApiData { users: User[]}const data: ApiData = response
// unknown + Type Guardconst data: unknown = responseif (isApiData(data)) { // data ist jetzt ApiData}tsconfig.json für Vue
Abschnitt betitelt „tsconfig.json für Vue“{ "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "jsx": "preserve", "jsxImportSource": "vue", "noEmit": true, "isolatedModules": true, "verbatimModuleSyntax": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]}Dokumentation
Abschnitt betitelt „Dokumentation“Vue + TypeScript:
TypeScript Grundlagen: