Vue 2.5 & TypeScript: API 參數中的類型推導
在剛剛發布的 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 或者是一個包含 ThisType 的 intersection,則 this 的類型為T。
- 否則,如果編譯選項 --noImplicitThis 打開,並且 object literal 通過 contextual typing 得到的類型不包含 ThisType,this 的類型為所得到的 contextual type。
- 否則,如果編譯選項 --onImplicitThis 打開,this 的類型為將這個方法所包含的 object literal 的類型。
- 否則,this 的類型為 any。
因此,如果一個方法通過傳入的參數來修改 this 的值(比如 Vue 將props中的值自動加入 Vue instance 的屬性中),可以用 ThisType 來標記這個 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 的類型有
- ThisTypedComponentOptionsWithArrayProps
- ThisTypedComponentOptionsWithRecordProps
分別對應使用 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 components `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 components `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 上的屬性了。
Reference
- Upcoming TypeScript Changes in Vue 2.5
- Pull Request #14141 Microsoft/TypeScript
- Pull Request #5887 vuejs/vue
- Pull Request #6391 vuejs/vue
推薦閱讀:
TAG:Vue.js | TypeScript |
