
inb4: Dies ist kein weiteres "Einrichten" eines neuen Projekts mit Vue- und TypeScript-Tutorial. Lassen Sie uns etwas tiefer in komplexere Themen eintauchen!
typescript
ist fantastisch. Vue
ist großartig. Kein Zweifel, dass viele Leute versuchen, sie zusammen zu bündeln . Aus verschiedenen Gründen ist es jedoch schwierig, Ihre Vue
App wirklich Vue
. Lassen Sie uns herausfinden, was die Probleme sind und was getan werden kann, um sie zu lösen (oder zumindest die Auswirkungen zu minimieren).
TLDR
Wir haben diese wunderbare Vorlage mit Nuxt
, Vue
, Vuex
und jest
vollständig getippt. Installieren Sie es einfach und alles wird für Sie abgedeckt. Weitere Informationen finden Sie in den Dokumenten .
Und wie gesagt, ich werde Sie aus drei Gründen nicht durch die Grundeinstellungen führen:
- Es gibt viele existierende Tutorials darüber
- Es gibt viele Tools, die Sie mit einem einzigen Klick
Nuxt
wie Nuxt
und vue-cli
mit typescript
Plugin - Wir haben bereits eine
wemake-vue-template
in der jedes Setup, über das ich sprechen werde, bereits behandelt wird
Komponententypen
Die erste gebrochene Erwartung, wenn Sie mit Vue
und typescript
und nachdem Sie Ihre Klassenkomponenten bereits eingegeben haben, ist, dass die Tags <template>
und <style>
immer noch nicht eingegeben werden. Lassen Sie mich Ihnen ein Beispiel zeigen:
<template> <h1 :class="$style.headr"> Hello, {{ usr }}! </h1> </template> <script lang="ts"> import Vue from 'vue' import Component from 'vue-class-component' import { Prop } from 'vue-property-decorator' @Component({}) export default class HelloComponent extends Vue { @Prop() user!: string } </script> <style module> .header { /* ... */ } </style>
Ich habe hier zwei Tippfehler gemacht: {{ usr }}
anstelle von {{ user }}
und $style.headr
anstelle von $style.header
. Wird mich typescript
vor diesen Fehlern bewahren? Nein, das wird es nicht.
Was kann getan werden, um das Problem zu beheben? Nun, es gibt mehrere Hacks.
Vorlage eingeben
Sie können Vetur
mit der Option vetur.experimental.templateInterpolationService
, um Ihre Vorlagen zu überprüfen. Ja, dies ist nur eine Editor-basierte Prüfung und kann noch nicht im CI verwendet werden. Das Vetur
Team arbeitet jedoch hart daran, eine CLI bereitzustellen, die dies ermöglicht. Verfolgen Sie die Originalausgabe, falls Sie interessiert sind.

Die zweite Option sind zwei Schreib-Snapshot-Tests mit jest
. Es werden viele vorlagenbasierte Fehler abgefangen. Und es ist recht günstig in der Wartung.
Die Kombination dieser beiden Tools bietet Ihnen eine schöne Entwicklererfahrung mit schnellem Feedback und einer zuverlässigen Möglichkeit, Fehler im CI zu erkennen.
Schreibstile
Die Eingabe von css-module
wird auch von mehreren externen Tools abgedeckt:
Die Hauptidee dieser Tools besteht darin, .d.ts
css-module
.d.ts
und daraus .d.ts
Deklarationsdateien zu erstellen. Dann werden Ihre Stile vollständig eingegeben. Es ist immer noch nicht für Nuxt
oder Vue
implementiert, aber Sie können dieses Problem auf Fortschritt Nuxt
.
Ich persönlich verwende jedoch keines dieser Tools in meinen Projekten. Sie könnten für Projekte mit großen Codebasen und vielen Stilen nützlich sein, aber ich kann nur Schnappschüsse.
Styleguides mit visuellen Regressionstests helfen ebenfalls sehr. @storybook/addon-storyshots
ist ein schönes Beispiel für diese Technik.
Vuex
Das nächste große Ding ist Vuex
. Es hat einige eingebaute Komplexität für die Eingabe:
const result: Promise<number> = this.$store.dispatch('action_name', { payload: 1 })
Das Problem ist, dass 'action_name'
möglicherweise nicht vorhanden ist, andere Argumente 'action_name'
oder einen anderen Typ 'action_name'
. Das erwarten Sie nicht für eine vollständig typisierte App.
Was sind die vorhandenen Lösungen?
Vuex-Klasse
vuex-class
ist eine Reihe von Dekoratoren, die einen einfachen Zugriff Ihrer klassenbasierten Komponenten auf die Vuex
Interna ermöglichen.
Es ist jedoch nicht sicher geschrieben, da es die Zustandsarten, Getter, Mutationen und Aktionen nicht beeinträchtigen kann.

Natürlich können Sie Arten von Eigenschaften manuell mit Anmerkungen versehen.

Aber was werden Sie tun, wenn sich die tatsächliche Art Ihres Zustands, Ihrer Getter, Mutationen oder Aktionen ändert? Sie haben eine versteckte Typinkongruenz.
vuex-einfach
vuex-simple
hilft uns vuex-simple
. Es bietet tatsächlich eine völlig andere Möglichkeit, Ihren Vuex
Code zu schreiben, und das macht ihn typsicher. Werfen wir einen Blick darauf:
import { Action, Mutation, State, Getter } from 'vuex-simple' class MyStore { // State @State() public comments: CommentType[] = [] // Getters @Getter() public get hasComments (): boolean { return Boolean(this.comments && this.comments.length > 0) } // Mutations @Mutation() public setComments (payload: CommentType[]): void { this.comments = updatedComments } // Actions @Action() public async fetchComments (): Promise<CommentType[]> { // Calling some API: const commentsList = await api.fetchComments() this.setComments(commentsList) // typed mutation return commentsList } }
Später kann dieses typisierte Modul wie Vuex
in Ihrem Vuex
registriert werden:
import Vue from 'vue' import Vuex from 'vuex' import { createVuexStore } from 'vuex-simple' import { MyStore } from './store' Vue.use(Vuex) // Creates our typed module instance: const instance = new MyStore() // Returns valid Vuex.Store instance: export default createVuexStore(instance)
Jetzt haben wir eine 100% native Vuex.Store
Instanz und alle darin enthaltenen Vuex.Store
. Um diesen typisierten Speicher in der Komponente zu verwenden, können wir nur eine Codezeile schreiben:
import Vue from 'vue' import Component from 'nuxt-class-component' import { useStore } from 'vuex-simple' import MyStore from './store' @Component({}) export default class MyComponent extends Vue { // That's all we need! typedStore: MyStore = useStore(this.$store) // Demo: will be typed as `Comment[]`: comments = typedStore.comments }
Jetzt haben wir Vuex
eingegeben, das sicher in unserem Projekt verwendet werden kann.
Wenn wir etwas in unserer Geschäftsdefinition ändern, wird es automatisch auf die Komponenten übertragen, die dieses Geschäft verwenden. Wenn etwas ausfällt, wissen wir es so schnell wie möglich.
Es gibt auch verschiedene Bibliotheken, die dasselbe tun, aber unterschiedliche APIs haben. Wählen Sie, was am besten zu Ihnen passt.
API-Aufrufe
Wenn wir Vuex
richtig eingerichtet haben, müssen wir es mit Daten füllen.
Schauen wir uns noch einmal unsere Aktionsdefinition an:
@Action() public async fetchComments (): Promise<CommentType[]> { // Calling some API: const commentsList = await api.fetchComments() // ... return commentsList }
Wie können wir wissen, dass es wirklich eine Liste von CommentType
und nicht eine einzelne number
oder eine Reihe von AuthorType
Instanzen AuthorType
?
Wir können den Server nicht steuern. Und der Server könnte tatsächlich den Vertrag brechen. Oder wir können einfach die falsche api
Instanz übergeben, einen Tippfehler in der URL machen oder was auch immer.
Wie können wir sicher sein? Wir können die Laufzeit eingeben! Lassen Sie mich Ihnen io-ts
vorstellen:
import * as ts from 'io-ts' export const Comment = ts.type({ 'id': ts.number, 'body': ts.string, 'email': ts.string, }) // Static TypeScript type, that can be used as a regular `type`: export type CommentType = ts.TypeOf<typeof Comment>
Was machen wir hier?
- Wir definieren eine Instanz von
ts.type
mit Feldern, die zur Laufzeit überprüft werden müssen, wenn wir eine Antwort vom Server erhalten - Wir definieren einen statischen Typ, der für Anmerkungen ohne zusätzliche Boilerplate verwendet werden soll
Und später können wir es unsere api
Aufrufe verwenden:
import * as ts from 'io-ts' import * as tPromise from 'io-ts-promise' public async fetchComments (): Promise<CommentType[]> { const response = await axios.get('comments') return tPromise.decode(ts.array(Comment), response.data) }
Mit Hilfe von io-ts-promise
können wir ein Promise
in einem fehlgeschlagenen Zustand zurückgeben, wenn die Antwort vom Server nicht mit einem ts.array(Comment)
Typ ts.array(Comment)
. Es funktioniert wirklich wie eine Validierung.
fetchComments() .then((data) => /* ... */ .catch(/* Happens with both request failure and incorrect response type */)
Darüber hinaus ist die Annotation vom Rückgabetyp mit der .decode
Methode .decode
. Und Sie können dort keinen zufälligen Unsinn machen:

Durch die Kombination von Laufzeit- und statischen Überprüfungen können wir sicher sein, dass unsere Anforderungen nicht aufgrund der Typinkongruenz fehlschlagen.
Um jedoch zu 100% sicher zu sein, dass alles funktioniert, würde ich die Verwendung vertragsbasierter Tests empfehlen: Schauen Sie sich als Beispiel den pact
an. Und überwachen Sie Ihre App mit Sentry
.
Vue Router
Das nächste Problem ist, dass this.$router.push({ name: 'wrong!' })
Funktioniert nicht so, wie wir es wollen.
Ich würde sagen, dass es ideal wäre, vom Compiler gewarnt zu werden, dass wir in die falsche Richtung routen und diese Route nicht existiert.
Das ist aber nicht möglich. Und es kann nicht viel getan werden: Es gibt viele dynamische Routen, Regex, Fallbacks, Berechtigungen usw., die irgendwann brechen können. Die einzige Möglichkeit besteht darin, dies jeweils zu testen this.$router
Aufruf in Ihrer App.
vue-test-utils
Apropos Tests Ich habe keine Ausreden, ganz zu schweigen von @vue/test-utils
, die auch Probleme beim Tippen haben.
Wenn wir versuchen, unsere neue glänzende Komponente mit der typedStore
Eigenschaft zu testen, werden wir feststellen, dass wir dies gemäß dem typescript
nicht tun können:

Warum passiert das? Dies liegt daran mount()
Aufruf von mount()
nichts über den Typ Ihrer Komponente weiß, da alle Komponenten standardmäßig den Typ VueConstructor<Vue>
:

Hierher kommen alle Probleme. Was kann getan werden?
Sie können vuetype
, um YouComponent.vue.d.ts
vuetype
zu erstellen, die Ihren Tests den genauen Typ der YouComponent.vue.d.ts
Komponente mitteilen.
Sie können dieses Problem auch für den Fortschritt verfolgen.
Aber ich mag diese Idee nicht. Dies sind Tests, sie können fehlschlagen. Keine große Sache.
Deshalb (wrapper.vm as any).whatever
ich mich an (wrapper.vm as any).whatever
. Das spart mir viel Zeit, um Tests zu schreiben. Aber verdirbt Developer Experience ein wenig.
Treffen Sie hier Ihre eigene Entscheidung:
- Verwenden
vuetype
gesamten vuetype
- Wenden Sie es teilweise auf die wichtigsten Komponenten mit der größten Anzahl von Tests an und aktualisieren Sie es regelmäßig
- Verwenden Sie
any
als Fallback
Fazit
Die durchschnittliche Unterstützung für typescript
im Vue
Ökosystem hat in den letzten Jahren zugenommen:
Nuxt
zuerst nuxt-ts
eingeführt und nuxt-ts
nun standardmäßig ts
Builds ausVue@3
bietet eine verbesserte typescript
Unterstützung- Weitere Apps und Plugins von Drittanbietern bieten Typdefinitionen
Aber es ist im Moment produktionsbereit. Dies sind nur Dinge, die verbessert werden müssen! Das Schreiben von typsicherem Vue
Code verbessert Ihre Entwicklererfahrung erheblich und ermöglicht es Ihnen, sich auf die wichtigen Dinge zu konzentrieren, während Sie das schwere Heben dem Compiler überlassen.
Was sind Ihre bevorzugten Hacks und Tools zum Eingeben von Vue
Apps? Lassen Sie es uns im Kommentarbereich diskutieren.