在剛剛釋出的 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 上的屬性了。