Перейти к содержанию

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, должны автоматически выполнять эти дополнения в своих собственных определениях типов.

На размещение этого дополнения распространяются те же ограничения, что и на дополнения глобальных свойств.

См. также:

Типизация глобальных пользовательских директив ​

См.: Типизация глобальных пользовательских директив

TypeScript с Options API