TypeScript con Options API
Si presume che tu abbia già letto Usare Vue con TypeScript.
TIP
Anche se Vue supporta l'uso di TypeScript con Options API, è consigliato utilizzare Vue con TypeScript tramite Composition API poiché offre un'inferenza dei tipi più semplice, efficiente e robusta.
Tipizzare le props dei componenti
Ottenere dei tipi per le props nell'Options API richiede di avvolgere il componente con defineComponent()
. In questo modo, Vue è in grado di ottenere i tipi per le props in base all'opzione props
, tenendo conto di altre opzioni come required: true
e default
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
// type inference abilitato
props: {
name: String,
id: [Number, String],
msg: { type: String, required: true },
metadata: null
},
mounted() {
this.name // tipo: string | undefined
this.id // tipo: number | string | undefined
this.msg // tipo: string
this.metadata // tipo: any
}
})
Tuttavia, le opzioni di props
al runtime supportano solo l'uso di funzioni costruttori come tipo di prop - non c'è modo di specificare tipi complessi come oggetti con proprietà nidificate o chiamate di funzione.
Per annotare tipi di prop complessi, possiamo utilizzare il tipo di utilità PropType
:
ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
// fornia,o un tipo più specifico per l'oggetto
type: Object as PropType<Book>,
required: true
},
// funzioni
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // stringa
this.book.year // numero
// TS Error: argument of type 'string' is not
// assignable to parameter of type 'number'
this.callback?.('123')
}
})
Caveats
Se la versione di TypeScript è inferiore a 4.7
, bisogna fare attenzione quando si utilizzano funzioni per le opzioni validator
e default
delle props - assicurarsi di utilizzare le arrow functions:
ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
year?: number
}
export default defineComponent({
props: {
bookA: {
type: Object as PropType<Book>,
// Assicurati di utilizzare le arrow functions se TypeScript ha una versione minore della 4.7
default: () => ({
title: 'Arrow Function Expression'
}),
validator: (book: Book) => !!book.title
}
}
})
Questa pratica impedisce a TypeScript di dover inferire il tipo this
all'interno di queste funzioni, che, sfortunatamente, può causare un fallimento nell'inferenza del tipo. Era un limite di design, ed è stato migliorato in TypeScript 4.7.
Tipizzare gli emits dei componenti
Possiamo dichiarare il tipo di payload previsto per un evento emesso utilizzando la sintassi oggetto dell'opzione emits
. Inoltre, tutti gli eventi emessi non dichiarati genereranno un errore di tipo quando vengono chiamati.
ts
import { defineComponent } from 'vue'
export default defineComponent({
emits: {
addBook(payload: { bookName: string }) {
// esegue la runtime validation
return payload.bookName.length > 0
}
},
methods: {
onSubmit() {
this.$emit('addBook', {
bookName: 123 // Type error!
})
this.$emit('non-declared-event') // Type error!
}
}
})
Tipizzare le Computed Properties
Una computed property ottiene il suo tipo basandosi sul suo valore di ritorno:
ts
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
greeting() {
return this.message + '!'
}
},
mounted() {
this.greeting // type: string
}
})
In alcuni casi, potresti voler annotare esplicitamente il tipo di una computed property per garantire che la sua implementazione sia corretta:
ts
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
// mostra esplicitamente il tipo di return
greeting(): string {
return this.message + '!'
},
// annotating a writable computed property
greetingUppercased: {
get(): string {
return this.greeting.toUpperCase()
},
set(newValue: string) {
this.message = newValue.toUpperCase()
}
}
}
})
Le annotazioni esplicite possono essere necessarie in alcuni casi limite in cui TypeScript non riesce a inferire il tipo di una proprietà calcolata a causa di cicli di inferenza circolare.
Tipizzazione degli Event Handlers
Quando si gestiscono eventi nativi del DOM, può essere utile specificare correttamente il tipo dell'argomento passato all'handler. Vediamo un esempio:
vue
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event) {
// `event` ha in modo implicito il tipo `any`
console.log(event.target.value)
}
}
})
</script>
<template>
<input type="text" @change="handleChange" />
</template>
Senza annotazione di tipo, l'argomento event
avrà implicitamente un tipo any
. Ciò causerà anche un errore di TS se "strict": true
o "noImplicitAny": true
sono impostati nel file tsconfig.json
. È quindi consigliabile annotare esplicitamente l'argomento degli event handlers. Inoltre, potrebbe essere necessario utilizzare le asserzioni di tipo quando si accedono alle proprietà di event
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
}
})
Augmenting Global Properties
Alcuni plugin installano proprietà disponibili globalmente su tutte le istanze del componente tramite app.config.globalProperties
. Ad esempio, potremmo installare this.$http
per il recupero dei dati o this.$translate
per l'internazionalizzazione. Per rendere ciò compatibile con TypeScript, Vue espone un'interfaccia ComponentCustomProperties
progettata per essere estesa tramite TypeScript module augmentation:
ts
import axios from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
Guarda anche:
Posizione dell'aumento di tipo
È possibile inserire questa estensione di tipo in un file .ts
, o in un file *.d.ts
a livello di progetto. In entrambi i casi, assicurarsi che sia incluso in tsconfig.json
. Per gli autori di librerie / plugin, questo file dovrebbe essere specificato nella proprietà types
in package.json
.
Per poter sfruttare l'estensione di modulo, è necessario assicurarsi che l'aumento sia posizionato in un modulo TypeScript. Questo per dire, il file deve contenere almeno un'istruzione import
o export
al livello superiore, anche se è solo export {}
. Se l'aumento è posizionato al di fuori di un modulo, sovrascriverà i tipi originali invece di estenderli!
ts
// Non funge, sovrascrive i tipi originali
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
ts
// Funziona correttamente
export {}
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
Augmenting Custom Options
Alcuni plugin, come vue-router
, forniscono il supporto per opzioni personalizzate per i componenti, come ad esempio beforeRouteEnter
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
beforeRouteEnter(to, from, next) {
// ...
}
})
Senza un'estensione di tipo adeguata, gli argomenti di questo hook avranno implicitamente il tipo any
. Possiamo estendere l'interfaccia ComponentCustomOptions
per supportare queste opzioni personalizzate:
ts
import { Route } from 'vue-router'
declare module 'vue' {
interface ComponentCustomOptions {
beforeRouteEnter?(to: Route, from: Route, next: () => void): void
}
}
Ora l'opzione beforeRouteEnter
avrà il tipo corretto. Nota che questo è solo un esempio: librerie ben tipizzate come vue-router
dovrebbero automaticamente eseguire queste estensioni nelle proprie definizioni di tipo.
La posizione di questa estensione è soggetta alle stesse restrizioni delle estensioni di proprietà globali.
Guarda anche: