外观
使用选项API的TypeScript
此页面假设您已经阅读了使用TypeScript与Vue的概述。
提示
尽管Vue支持使用选项API的TypeScript使用,但建议通过组合式API使用Vue,因为它提供更简单、更高效、更稳健的类型推断。
编写组件属性类型
在选项API中,对props的类型推断需要使用defineComponent()包装组件。通过它,Vue能够根据props选项推断props的类型,并考虑额外的选项,如required: true和default
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,在使用函数值作为validator和default 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.json的types属性中指定。
为了利用模块增强,您需要确保增强放置在一个TypeScript模块中。也就是说,文件需要至少包含一个顶层import或export,即使它只是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 这样的类型良好的库应该在其自己的类型定义中自动执行这些增强。
这种增强的位置受到与全局属性增强相同的限制。
另请参阅