TypeScript с Options API
Предполагается, что вы уже прочитали главу Использование Vue с TypeScript.
Совет
Хотя Vue поддерживает использование TypeScript с помощью Options API, рекомендуется использовать Vue с TypeScript через API Composition, так как он предлагает более простой, эффективный и надежный вывод типов.
Типизация пропсов компонента
Вывод типа для пропсов в Options API требует обёртывания компонента с помощью defineComponent()
. С его помощью Vue может определять типы пропсов на основе свойства props
, принимая во внимание дополнительные параметры, такие как require: true
и default
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
// вывод типа включен
props: {
name: String,
id: [Number, String],
msg: { type: String, required: true },
metadata: null
},
mounted() {
this.name // тип: string | undefined
this.id // тип: number | string | undefined
this.msg // тип: string
this.metadata // тип: any
}
})
Однако параметры props
во время выполнения поддерживают только использование функций конструктора в качестве типа параметра — нет возможности указать сложные типы, такие как объекты с вложенными свойствами или сигнатуры вызовов функций.
Для аннотирования сложных типов пропсов мы можем использовать служебный тип PropType
:
ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
// предоставляем более конкретный тип для `Object`.
type: Object as PropType<Book>,
required: true
},
// таким же образом аннотируем функции
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // string
this.book.year // number
// Ошибка TS: аргумент типа 'string' не является
// соответствующим параметру типа 'number'
this.callback?.('123')
}
})
Оговорки
Если ваша версия TypeScript меньше, чем 4.7
, вы должны быть осторожны при использовании значений функций для опций validator
и default
— обязательно используйте стрелочные функции:
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>,
// Если версия вашего TypeScript меньше 4.7, обязательно используйте стрелочные функции
default: () => ({
title: 'Arrow Function Expression'
}),
validator: (book: Book) => !!book.title
}
}
})
Это избавляет TypeScript от необходимости выводить тип this
внутри этих функций, что, к сожалению, может привести к неудачному выводу типа. Это было предыдущее ограничение дизайна, а теперь улучшено в TypeScript 4.7.
Типизация событий компонента
Мы можем объявить ожидаемый тип полезной нагрузки для испускаемого события, используя объектный синтаксис свойства emits
. Кроме того, все необъявленные эмиттируемые события при вызове будут выдавать ошибку типа:
ts
import { defineComponent } from 'vue'
export default defineComponent({
emits: {
addBook(payload: { bookName: string }) {
// проверка во время выполнения
return payload.bookName.length > 0
}
},
methods: {
onSubmit() {
this.$emit('addBook', {
bookName: 123 // Ошибка типа!
})
this.$emit('non-declared-event') // Ошибка типа!
}
}
})
Типизация вычисляемых свойств
Вычисляемое свойство определяет свой тип на основе возвращаемого значения:
ts
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Привет!'
}
},
computed: {
greeting() {
return this.message + '!'
}
},
mounted() {
this.greeting // тип: string
}
})
В некоторых случаях вам может понадобиться явно указать тип вычисляемого свойства, чтобы убедиться в правильности его реализации:
ts
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Привет!'
}
},
computed: {
// явно аннотируем возвращаемый тип
greeting(): string {
return this.message + '!'
},
// аннотирование вычисляемого свойства, доступного для записи
greetingUppercased: {
get(): string {
return this.greeting.toUpperCase()
},
set(newValue: string) {
this.message = newValue.toUpperCase()
}
}
}
})
Явные аннотации также могут потребоваться в некоторых крайних случаях, когда TypeScript не может определить тип вычисляемого свойства из-за зацикленного вывода.
Типизация обработчиков событий
При работе с собственными событиями DOM может оказаться полезным правильно вводить аргумент, который мы передаем обработчику. Давайте рассмотрим этот пример:
vue
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event) {
// `Событие` неявно имеет тип `any` (`любой`)
console.log(event.target.value)
}
}
})
</script>
<template>
<input type="text" @change="handleChange" />
</template>
Без аннотации типа аргумент event
будет неявно иметь тип any
. Это также приведет к ошибке TS, если "strict": true"
или "noImplicitAny": true
используются в файле tsconfig.json
. Поэтому рекомендуется явно аннотировать аргументы обработчиков событий. Кроме того, вам может понадобиться использовать утверждения типов при обращении к свойствам event
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
}
})
Расширение глобальных свойств
Некоторые плагины устанавливают глобально доступные свойства для всех экземпляров компонента через app.config.globalProperties
. Например, мы можем установить this.$http
для получения данных или this.$translate
для интернационализации. Чтобы всё это хорошо сочеталось с TypeScript, Vue предоставляет интерфейс ComponentCustomProperties
, предназначенный для дополнения с помощью расширения модулей TypeScript:
ts
import axios from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
См. также:
Размещение расширения типа
Мы можем поместить это расширение типа в файл .ts
или в общий для проекта файл *.d.ts
. В любом случае убедитесь, что он включен в tsconfig.json
. Для авторов библиотек/плагинов этот файл должен быть указан в свойстве types
в файле package.json
.
Чтобы воспользоваться преимуществами расширения модуля, вам нужно убедиться, что расширение помещено в модуль TypeScript. То есть файл должен содержать хотя бы один верхнеуровневый import
или export
, даже если это просто export {}
. Если дополнение размещено вне модуля, оно перезапишет исходные типы, а не дополнит их!
ts
// Не работает, перезаписывает исходные типы.
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
ts
// Работает правильно
export {}
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
Расширение пользовательских опций
Некоторые плагины, например vue-router
, обеспечивают поддержку пользовательских опций компонента, таких как beforeRouteEnter
:
ts
import { defineComponent } from 'vue'
export default defineComponent({
beforeRouteEnter(to, from, next) {
// ...
}
})
Без надлежащего дополнения типов аргументы этого хука будут неявно иметь тип any
. Мы можем дополнить интерфейс ComponentCustomOptions
для поддержки этих пользовательских опций:
ts
import { Route } from 'vue-router'
declare module 'vue' {
interface ComponentCustomOptions {
beforeRouteEnter?(to: Route, from: Route, next: () => void): void
}
}
Теперь опция beforeRouteEnter
будет набрана правильно. Обратите внимание, что это всего лишь пример — хорошо типизированные библиотеки, такие как vue-router
, должны автоматически выполнять эти дополнения в своих собственных определениях типов.
На размещение этого дополнения распространяются те же ограничения, что и на дополнения глобальных свойств.
См. также: