使用 TypeScript 写 Vue 组件时,有两种推荐形式:javascript
Vue.extend()
:使用基础 Vue 构造器,建立一个“子类”。此种写法与 Vue 单文件组件标准形式最为接近,惟一不一样仅是组件选项须要被包裹在 Vue.extend()
中。vue-class-component
:一般与 vue-property-decorator
一块儿使用,提供一系列装饰器,能让咱们书写类风格的 Vue 组件。两种形式输出结果一致,同是建立一个 Vue 子类,但在书写组件选项如 props,mixin 时,有些不一样。特别是当你使用 Vue.extend()
时,为了让 TypeScript 正确推断类型,你将不得不作一些额外的处理。接下来,咱们来聊一聊它们的细节差别。vue
因为组件实例的做用域是孤立的,当从父组件传递数据到子组件时,咱们一般使用 Prop 选项。同时,为了确保 Prop 的类型安全,咱们会给 Prop 添加指定类型验证,形式以下:java
export default {
props: {
someProp: {
type: Object,
required: true,
default: () => ({ message: 'test' })
}
}
}
复制代码
咱们定义了一个 someProp,它的类型是 Object。git
使用 JavaScript 时,这并无什么不对的地方,但当你使用 TypeScript 时,这有点不足,咱们并不能获得有关于 someProp 更多有用的信息(好比它含有某些属性),甚至在 TypeScript 看来,这将会是一个 any 类型:github
这意味着咱们可使用 someProp 上的任意属性(存在或者是不存在的)均可以经过编译。为了防止此种状况的发生,咱们将会给 Prop 添加类型注释。typescript
使用 Vue.extend()
方法添加类型注释时,须要给 type 断言:安全
import Vue from 'vue'
interface User {
name: string,
age: number
}
export default Vue.extend({
props: {
testProps: {
type: Object as () => User
}
}
})
复制代码
当组件内访问 testProps 时,便能获得相关提示:函数
然而,你必须以函数返回值的形式断言,并不能直接断言:ui
export default Vue.extend({
props: {
testProps: {
type: Object as User
}
}
})
复制代码
它会给出错误警告,User 接口并无实现原生 Object 构造函数所执行的方法: Type 'ObjectConstructor' cannot be converted to type 'User'. Property 'id' is missing in type 'ObjectConstructor'.this
实际上,咱们可从 Prop type declaration :
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];
export interface PropOptions<T=any> {
type?: Prop<T> | Prop<T>[];
required?: boolean;
default?: T | null | undefined | (() => object); validator?(value: T): boolean; } 复制代码
可知 Prop type 能够以两种不一样方式出现:
T & object
用于下降优先级,当两种方式同时知足时取第一种,其次它还能够用于标记构造函数不该该返回原始类型)。当咱们指定 type 类型为 String/Number/Boolean/Array/Object/Date/Function/Symbol 原生构造函数时,Prop 会返回它们各自签名的返回值。
当 type 类型为 String 构造函数时,它的调用签名返回为 string:
// lib.es5.d.ts
interface StringConstructor {
new(value?: any): String;
(value?: any): string;
readonly prototype: String;
fromCharCode(...codes: number[]): string;
}
复制代码
而这也是上文中,当指定 type 类型为 Object 构造函数时,通过 Vue 的声明文件处理,TypeScript 推断出为 any 类型的缘由:
interface ObjectConstructor {
new(value?: any): Object;
(): any;
(value: any): any;
// 其它属性 ....
}
复制代码
相似的,当咱们使用关键字 as
断言 Object 为 () => User
时,它能推断出为 User 。
从 type 第二部分可知,除传入原生构造函数外,咱们还可传入自定义类:
此外,这里有个 PR 暴露一个更直观的类型( Vue 2.6 版本才能够用):
props: {
testProp: Object as PropTypes<{ test: boolean }>
}
复制代码
得益于 vue-propperty-decorator Prop 修饰器,当给 Prop 增长类型推断时,这些将变得简单:
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class Test extends Vue {
@Prop({ type: Object })
private test: { value: string }
}
复制代码
当咱们在组件内访问 test
时,便能获取它正确的类型信息。
mixins 是一种分发 Vue 组件中可复用功能的一种方式。当在 TypeScript 中使用它时,咱们但愿获得有关于 mixins 的类型信息。
当你使用 Vue.extends()
时,这有点困难,它并不能推断出 mixins 里的类型:
// ExampleMixin.vue
export default Vue.extend({
data () {
return {
testValue: 'test'
}
}
})
// other.vue
export default Vue.extend({
mixins: [ExampleMixin],
created () {
this.testValue // error, testValue 不存在!
}
})
复制代码
咱们须要稍做修改:
// other.vue
export default ExampleMixin.extend({
mixins: [ExampleMixin],
created () {
this.testValue // 编译经过
}
})
复制代码
但这会存在一个问题,当使用多个 mixins 且推断出类型时,这将没法工做。而在这个 Issuse 中官方也明确表示,这没法被修改。
使用 vue-class-component 这会方便不少:
// ExampleMixin.vue
import Vue from 'vue'
import Component from 'vue-class-component'
@Component
export class ExampleMixin extends Vue {
public testValue = 'test'
}
// other.vue
import Component, { mixins } from 'vue-class-component'
import ExampleMixin from 'ExampleMixin.vue'
@Component({
components: {
ExampleMixin
}
})
export class MyComp extends mixins(ExampleMixin) {
created () {
console.log(this.testValue) // 编译经过
}
}
复制代码
也支持能够传入多个 mixins。
作为 Vue 中最正统的方法(与标准形式最为接近),Vue.extends()
有着本身的优点,在 VScode Vetur 插件辅助下,它能正确提示子组件上的 Props:
而类作为 TypeScript 特殊的存在(它既能够做为类型,也能够做为值),当咱们使用 vue-class-component
并经过 $refs
绑定为子类组件时,便能获取子组件上暴露的类型信息:
当你在 Vue 中使用 TypeScript 时,所遇到的第一个问题便是在 ts 文件中找不到 .vue 文件,即便你所写的路径并无问题:
在 TypeScript 中,它仅识别 js/ts/jsx/tsx 文件,为了让它识别 .vue 文件,咱们须要显式告诉 TypeScript,vue 文件存在,而且指定导出 VueConstructor:
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
复制代码
可是,这引发了另外一个问题,当咱们导入一个并不存在的 .vue 文件时,也能经过编译:
是的,这在情理之中。
当我尝试在 .vue 文件中导入已存在或者不存在的 .vue 文件时,却获得不一样的结果:
文件不存在时:
文件存在时:
文件不存在时,引用 Vue 的声明文件。文件存在时,引用正确的文件定义。
这让人很困惑,而这些都是 Vetur 的功劳。
在这个 PR 下,我找到相关解答:这个 PR 里,Vetur 提供解析其余 .vue 文件的功能,以便能获取正确的信息,当 .vue 文件不存在时,会读取 .d.ts 里的信息。