跳转到内容

使用选项API的TypeScript

此页面假设您已经阅读了使用TypeScript与Vue的概述。

提示

尽管Vue支持使用选项API的TypeScript使用,但建议通过组合式API使用Vue,因为它提供更简单、更高效、更稳健的类型推断。

编写组件属性类型

在选项API中,对props的类型推断需要使用defineComponent()包装组件。通过它,Vue能够根据props选项推断props的类型,并考虑额外的选项,如required: truedefault

ts
import { defineComponent } from 'vue'

export default defineComponent({
  // type inference enabled
  props: {
    name: String,
    id: [Number, String],
    msg: { type: String, required: true },
    metadata: null
  },
  mounted() {
    this.name // type: string | undefined
    this.id // type: number | string | undefined
    this.msg // type: string
    this.metadata // type: any
  }
})

但是,运行时props选项仅支持使用构造函数作为prop的类型 - 无法指定复杂类型,如具有嵌套属性的对象或函数调用签名。

为了注释复杂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: {
      // provide more specific type to `Object`
      type: Object as PropType<Book>,
      required: true
    },
    // can also annotate functions
    callback: Function as PropType<(id: number) => void>
  },
  mounted() {
    this.book.title // string
    this.book.year // number

    // TS Error: argument of type 'string' is not
    // assignable to parameter of type 'number'
    this.callback?.('123')
  }
})

注意事项

如果您的TypeScript版本低于4.7,在使用函数值作为validatordefault prop选项时必须小心 - 确保使用箭头函数

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>,
      // Make sure to use arrow functions if your TypeScript version is less than 4.7
      default: () => ({
        title: 'Arrow Function Expression'
      }),
      validator: (book: Book) => !!book.title
    }
  }
})

这可以防止TypeScript在函数内部推断this的类型,不幸的是,这可能导致类型推断失败。这曾经是一个先前的设计限制,现在已经在TypeScript 4.7中得到了改进。

Component Emits 类型

我们可以使用emits选项的对象语法来声明预期的事件负载类型。此外,所有未声明的 emitted 事件在调用时都会抛出类型错误。

ts
import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // perform runtime validation
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // Type error!
      })

      this.$emit('non-declared-event') // Type error!
    }
  }
})

Computed Properties 类型

计算属性根据其返回值推断类型。

ts
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    greeting() {
      return this.message + '!'
    }
  },
  mounted() {
    this.greeting // type: string
  }
})

在某些情况下,您可能需要显式注解计算属性的类型,以确保其实现正确。

ts
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // explicitly annotate return type
    greeting(): string {
      return this.message + '!'
    },

    // annotating a writable computed property
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})

在某些边缘情况下,可能也需要显式注解,因为TypeScript由于循环推断循环而无法推断计算属性的类型。

Event Handlers 类型

当处理原生的DOM事件时,正确地为传递给处理器的参数进行类型注解可能很有用。让我们看看这个例子:

vue
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  methods: {
    handleChange(event) {
      // `event` implicitly has `any` type
      console.log(event.target.value)
    }
  }
})
</script>

<template>
  <input type="text" @change="handleChange" />
</template>

没有类型注解,event参数将隐式地具有类型any。如果使用"strict": true"noImplicitAny": true,则这将在tsconfig.json中导致TS错误。因此,建议显式注解事件处理程序的参数。此外,在访问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中。对于库/插件作者,此文件应在package.jsontypes属性中指定。

为了利用模块增强,您需要确保增强放置在一个TypeScript模块中。也就是说,文件需要至少包含一个顶层importexport,即使它只是export {}。如果增强放置在模块之外,它将覆盖原始类型而不是增强它们!

ts
// Does not work, overwrites the original types.
declare module 'vue' {
  interface ComponentCustomProperties {
    $translate: (key: string) => string
  }
}
ts
// Works correctly
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 这样的类型良好的库应该在其自己的类型定义中自动执行这些增强。

这种增强的位置受到与全局属性增强相同的限制

另请参阅

使用选项API的TypeScript已加载