在刚刚发布的 Vue.js 2.5 中加强了对 TypeScript 的支持,TypeScript 可以直接推导出 Vue.extend(options)
, Vue.component(options)
和 new Vue(options)
等 API 的参数中的 this
的类型,无需依赖 vue-class-component
这样的 decorator。
这个功能依赖于 TypeScript 在 2.4 版本中引入的一个新特性 ThisType
。ThisType 本身是一个不包含内容的 interface,其作用是人为地给定某些条件下 this
的类型。
/**
* Marker for contextual 'this' type
*/
interface ThisType<T> { }复制代码
在 TypeScript 2.4 后,一个 object literal 所包含的方法的内部,其 this
的类型为:
- 如果这个方法显示地声明了参数
this
,则 this 的类型为给定参数的类型。 - 否则,如果这个方法可以通过 contextual typing ,从方法的 signature 中得到
this
的类型,则this
就是这个类型。 - 否则,如果编译选项
--noImplicitThis
打开,并且 object literal 通过 contextual typing 得到的类型是ThisType<T>
或者是一个包含ThisType<T>
的 intersection,则this
的类型为T
。 - 否则,如果编译选项
--noImplicitThis
打开,并且 object literal 通过 contextual typing 得到的类型不包含ThisType<T>
,this
的类型为所得到的 contextual type。 - 否则,如果编译选项
--onImplicitThis
打开,this
的类型为将这个方法所包含的 object literal 的类型。 - 否则,
this
的类型为any
。
因此,如果一个方法通过传入的参数来修改 this
的值(比如 Vue 将 props
中的值自动加入 Vue instance 的属性中),可以用 ThisType<T>
来标记这个 this
的类型。
例如:
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
}
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // Strongly typed this
this.y += dy; // Strongly typed this
}
}
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);复制代码
在这里,makeObject
内部的 desc.methods
的类型为 M & ThisType<D & M>
,因此 moveBy
方法中的 this
的类型就是 D & M
,因此可以使用 this.x
,this.y
和 this.moveBy
。
在 Vue 2.5 的 TypeScript 类型声明文件中,使用了 ThisType
的类型有
分别对应使用 array 和使用 object 作为 props
的值的 component options。
// ThisTypedComponentOptionsWithArrayProps
export default Vue.extend({
props: ['prop1', 'prop2'],
})
// ThisTypedComponentOptionsWithRecordProps
export default Vue.extend({
props: {
prop1: {
type: Number,
default: 0,
}
}
})复制代码
使用这个类型作为参数的 API 包括:
function Vue (options)
(new Vue(options)
)Vue.extend(options)
Vue.component(options)
脱离这 3 个 API,是无法使用 Vue instance 中属性的。例如,
export default {
props: ['prop1', 'prop2'],
mounted() {
console.log(this.prop1) // error TS2551: Property 'prop1' does not exist on type ...
}
};
// 或者
const options = {
props: ['prop1', 'prop2'],
mounted() {
console.log(this.prop1) // error TS2551: Property 'prop1' does not exist on type ...
}
};
export default Vue.extend(options)复制代码
此外,mixin 和 global mixin 中声明的属性也不在这几个 API 的参数中的方法里所能推导的 this
类型当中。
回到上面所说的 2 个 component options 类型,
/**
* This type should be used when an array of strings is used for a component's `props` value.
*/
export type ThisTypedComponentOptionsWithArrayProps<V extends Vue, Data, Methods, Computed, PropNames extends string> =
object &
ComponentOptions<V, Data | ((this: Readonly<Record<PropNames, any>> & V) => Data), Methods, Computed, PropNames[]> &
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Record<PropNames, any>>>>;
/**
* This type should be used when an object mapped to `PropOptions` is used for a component's `props` value.
*/
export type ThisTypedComponentOptionsWithRecordProps<V extends Vue, Data, Methods, Computed, Props> =
object &
ComponentOptions<V, Data | ((this: Readonly<Props> & V) => Data), Methods, Computed, RecordPropsDefinition<Props>> &
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>;复制代码
这 2 个名字很长的类型其实都是使用 generic 的 type alias,内容都是 ComponentOptions 类型和一个 ThisType
类型的 intersection。在对这 2 个类型中的类型参数(包括 Data
,Methods
,Computed
,PropNames
或者 Props
)进行 type argument inference 的过程中,ThisType
部分实际上是作为 {}
被忽略的,options 中的 data
, methods
, computed
和 props
的类型被 ComponentOptions
捕获。
在此之后,同一个 type alias 表达式中的 ThisType
部分也获得了这几个类型参数,ThisType
中使用了 CombinedVueInstance 作为类型参数,而 CombinedVueInstance
的内容为:
export type CombinedVueInstance<Instance extends Vue, Data, Methods, Computed, Props> = Instance & Data & Methods & Computed & Props;复制代码
实际上就是包含用户定义的 data
,methods
,computed
,props
的 Vue instance,这里面的 Props
类型参数根据传入的参数所使用的 props
形式不同经过了又一次的转换。
在 Props
的转换过程中,如果传入的是一个 object,则会从其中的 type
属性的 constructor 和 default
属性的类型推断出这个 prop
的类型。
不过如果在 prop
的参数中不能推导出 prop
的类型,TypeScript 会编译错误,这是 Vue 2.5.2 版本的一个 bug。
这样,在之前提到的 3 个 API 的参数中,内部所包含的方法里,this
的类型就成为了 Instance & Data & Methods & Computed & Props
,我们就可以在里面使用 Vue instance 上的属性了。