在 2.5.0 版本中,Vue 大大改进了类型声明系统以更好地使用默认的基于对象的 API。html
意味着当咱们仅是安装 Vue 的声明文件时,一切也都将会按预期进行:vue
this
,就是 Vue;this
属性上,具备 Methods 选项上定义的同名函数属性;this
属性上;在这篇文章里,咱们来谈谈上述背后的故事。git
当咱们建立 Vue 实例,并在 Methods 上定义方法时, this
不只具备 Vue 实例上属性,同时也具备与 Methods 选项上同名的函数属性:github
new Vue({
methods: {
test () {
this.$el // Vue 实例上的属性
}
},
created () {
this.test() // methods 选项上同名的方法
this.$el // Vue 实例上的属性
}
})
复制代码
为了探究其原理,咱们把组件选项的声明改写成如下方式:typescript
定义 Methods:app
// methods 是 [key: string]: (this: Vue) => any 的集合
type Methods = Record<string, (this: Vue) => any>
复制代码
这会存在一个问题,Methods 上定义的方法里的 this
,所有都是 Vue 构造函数上的方法,而不能访问咱们自定义的方法。 咱们须要把 Vue 实例传进去:函数
type Methods<V> = Record<string, (this: V) => any>
复制代码
组件选项(一样也须要传实例):ui
interface ComponentOption<V> {
methods: Methods<V>,
created?(this: V): void
}
复制代码
咱们可使用它:this
declare function testVue<V extends Vue>(option: ComponentOption<V>): V 复制代码
此种情形下,咱们必须将组件实例的类型显式传入,从而使其编译经过:spa
interface TestComponent extends Vue {
test (): void
}
testVue<TestComponent>({
methods: {
test () {}
},
created () {
this.test() // 编译经过
this.$el // 经过
}
})
复制代码
这有点麻烦,为了使它能按咱们预期的工做,咱们定义了一个额外的 interface。
在 Vue 的声明文件里,使用了一种简单的方式:经过使用 ThisType<T>
映射类型,让 this
具备所须要的属性。
在 TypeScript 仓库 ThisType<T>
的 PR 下,有一个使用例子:
在这个例子中,经过对 methods 的值使用 ThisType<D & M>
,从而 TypeScript 推导出 methods 对象中 this
便是: { x: number, y: number } & { moveBy(dx: number, dy: number ): void }
。
与此相似,咱们可让 this
具备 Methods 上定义的同名函数属性:
type DefaultMethods<V> = Record<string, (this: V) => any>
interface ComponentOption<
V,
Methods = DefaultMethods<V>
> {
methods: Methods,
created?(): void
}
declare function testVue<V extends Vue, Methods> (
option: ComponentOption<V, Methods> & ThisType<V & Methods>
): V & Methods
testVue({
methods: {
test () {}
},
created () {
this.test() // 编译经过
this.$el // 实例上的属性
}
})
复制代码
在上面代码中,咱们:
[key: string]: (this: V) => any
的 Methods。ThisType
。ThisType<V & Methods>
标志着实例内的 this
便是 V 与 Methods 的交叉类型。{ test (): void }
,从而在实例内 this
便是:Vue & { test (): void }
;得益于上文中的 ThisType<T>
,Data 的处理有点相似与 Methods,惟一不一样之处 Data 可有两种不一样类型,Object 或者 Function。它的类型写法以下:
type DefaultData<V> = object | ((this: V) => object)
复制代码
一样,咱们也把 ComponentOption 与 testVue 稍做修改
interface ComponentOption<
V,
Data = DefaultData<V>,
Methods = DefaultMethods<V>
> {
data: Data
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Methods> ( option: ComponentOption<V, Data, Methods> & ThisType<V & Data & Methods> ): V & Data& Methods 复制代码
当 Data 是 Object 时,它能正常工做:
testVue({
data: {
testData: ''
},
created () {
this.testData // 编译经过
}
})
复制代码
当咱们传入 Function 时,它并不能:
TypeScript 推断出 Data 是 (() => { testData: string })
,这并非指望的 { testData: string }
,咱们须要对函数参数 options 的类型作少量修改,当 Data 传入为函数时,取函数返回值:
declare function testVue<V extends Vue, Data, Method>(
option: ComponentOption<V, Data | (() => Data), Method> & ThisType<V & Data & Method>
): V & Data & Method
复制代码
这时候编译能够经过:
testVue({
data () {
return {
testData: ''
}
},
created () {
this.testData // 编译经过
}
})
复制代码
Computed 的处理彷佛有点棘手:它与 Methods 不一样,当咱们在 Methods 中定义了一个方法,this
也会含有相同名字的函数属性,而在 Computed 中定义具备返回值的方法时,咱们指望 this
含有函数返回值的同名属性。
举个例子:
new Vue({
computed: {
testComputed () {
return ''
}
},
methods: {
testFunc () {}
},
created () {
this.testFunc() // testFunc 是一个函数
this.testComputed // testComputed 是 string,并非一个返回值为 string 的函数
}
})
复制代码
咱们须要一个映射类型,把定义在 Computed 内具备返回值的函数,映射为 key 为函数名,值为函数返回值的新类型:
type Accessors<T> = {
[K in keyof T]: (() => T[K])
}
复制代码
Accessors<T>
将会把类型 T,映射为具备相同属性名称,值为函数返回值的新类型,在类型推断时,此过程相反。
接着,咱们补充上例:
// Computed 是一组 [key: string]: any 的集合
type DefaultComputed = Record<string, any>
interface ComponentOption<
V,
Data = DefaultData<V>,
Computed = DefaultComputed,
Methods = DefaultMethods<V>
> {
data?: Data,
computed?: Accessors<Computed>
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Compted, Methods> (
option: ComponentOption<V, Data | (() => Data), Compted, Methods> & ThisType<V & Data & Compted & Methods>
): V & Data & Compted & Methods
testVue({
computed: {
testComputed () {
return ''
}
},
created () {
this.testComputed // string
}
})
复制代码
当调用 testVue 时,咱们传入一个属性为 testComputed () => ''
的 Computed,TypeScript 会尝试将类型映射至 Accessors<T>
,从而推导出 Computed 便是 { testComputed: string }
。
此外,Computed 具备另外一个写法:get 与 set 形式,咱们只须要把映射类型作相应补充便可:
interface ComputedOptions<T> {
get?(): T,
set?(value: T): void
}
type Accessors<T> = {
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]> } 复制代码
在上篇文章在 Vue 中使用 TypeScript 的一些思考(实践)中,咱们已经讨论了 Prop 的推导,在此再也不赘述。
此篇文章是对 Vue typings 的一次简单解读,但愿你们看得懂源码时,不要忘记了 Vue typings,毕竟 Vue typings 才是给程序行为以提示和约束的关键。