本文是《玩轉TypeScript工具類型》系列的最後一篇,包含了以下幾部份內容:vue
快捷跳轉react
ThisParameterType<Type>
提取一個函數類型顯式定義的this參數
,若是沒有顯式定義的this參數
,則返回unknown
。 這裏有以下幾個須要注意的點:typescript
this參數
只能叫this
,且必須在參數列表的第一個位置this
必須是顯式定義的this
參數在函數實際被調用的時候不存在,不須要顯式做爲參數傳入,而是經過call、apply或者是bind
等方法指定type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
複製代碼
從源碼能夠看出對於類型參數T
是要嚴格匹配(this: infer U, ...args: any[]) => any
格式的,因此對於this
參數的名稱和位置都是固定的。剩下的邏輯就是對this
參數的類型定義一個類型參數U
,在extends
判斷走true
分支時返回this
類型參數U
,false
分支就返回unknown
。安全
顯式的定義this
類型有助於咱們在函數內部安全的使用this
。markdown
function toHex(this: Number) {
return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex>) {
return toHex.apply(n);
}
複製代碼
注:定義了一個函數,要使用這個函數的類型,能夠直接使用typeof [funcName]
,能夠省去額外再定義一個類型聲明。數據結構
OmitThisParameter<Type>
有了ThisParameterType
獲取this
的類型,那麼如何將一個定義了this
參數類型的函數類型中的this
參數類型去掉呢? 這就是OmitThisParameter
作的事情。一句話歸納,就是對於沒有定義this
參數類型的函數類型,直接返回這個函數類型,若是定義了this
參數類型,就返回一個僅是去掉了this
參數類型的新函數類型。app
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
複製代碼
彷佛有點長,其實就是兩個嵌套的extends
條件判斷,分紅兩部分就很好理解了,首先是:函數
unknown extends ThisParameterType<T> ? T : ...
複製代碼
對於傳入的函數類型T
,首先使用ThisParameterType
獲取this
參數的類型,可能有兩種結果一種是成功拿到this
參數類型並返回,另外一種是unknown
。 因此若是返回的是unknown
,那麼就是走true
分支,直接返回T
。若是不是返回的unknown
,那麼就走false
分支,即:工具
T extends (...args: infer A) => infer R ? (...args: A) => R : T
複製代碼
又是一個條件判斷,即只要T
是一個合法的函數類型,就必定知足(...args: infer A) => infer R
,剩下的就是對參數定義一個類型參數A
,對返回值定義一個類型參數R
,返回(...args: A) => R
,這個新的函數類型已經不包含this
了。oop
function toHex(this: Number) {
return this.toString(16);
}
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
console.log(fiveToHex());
複製代碼
ThisType<Type>
這個工具類型很是特殊,第一個特殊之處就是它的源碼定義,是一個空接口:
/**
* Marker for contextual 'this' type
*/
interface ThisType<T> { }
複製代碼
那麼ThisType
的做用是什麼呢?正如官方註釋所寫的:做爲上下文this
類型的標記。
要使用
ThisType
必須保證noImplicitThis
配置開啓,後續咱們只討論開啓的狀況
那麼如何理解這句話呢?咱們須要從實際效果來理解,先看以下這段代碼:
let demo1 = {
a: 'lipengpeng',
test(msg: string) {
this;
}
};
複製代碼
它的this
類型是什麼呢?
this: {
a: string;
test(msg: string): void;
}
複製代碼
也能夠手動指定this
類型,好比:
let demo2 = {
a: 'lipengpeng',
test(this:{a: string}, msg: string) {
this;
}
};
複製代碼
這時的this
類型就是
this: {
a: string
}
複製代碼
其實這只是理想狀況下的this
類型分析,由於TypeScript
是經過靜態代碼分析推斷出的類型,在實際運行階段的this
是可能發生變化的,那麼咱們如何指定運行階段的this
類型呢?。
若是隻看如上兩種狀況,可能以爲不用ThisType
也足夠了,由於TypeScript
會推斷this
類型,可是這只是簡單狀況,就如咱們以前提到的,運行階段的this
是能夠改變的,因此僅是依賴代碼分析是沒法預測到將來的this
類型的,這時候就須要藉助咱們的主角——ThisType
了。 咱們繼續從實際的使用場景入手,實際開發中咱們定義一個對象有時候會給一個數據結構,就相似於Vue2.x Options API
:
let options = {
data: {
x: 0,
y: 0
},
methods: {
moveBy(dx: number, dy: number) {
this.x += dx;
this.y += dy;
}
}
}
複製代碼
咱們但願在moveBy
的this
對象上能夠直接獲取到data
對象中的x和y
。爲了實現這個功能,咱們須要對定義的數據結構作一些處理,讓methods
和data
中的屬性共享同一個this
對象,所以咱們須要一個工具方法makeObject
function makeObject(config) {
let data = config?.data || {}
let methods = config?.methods || {}
return {
...data,
...methods
}
}
let options = makeObject({
data: {
x: 0,
y: 0
},
methods: {
moveBy(dx: number, dy: number) {
this.x += dx;
this.y += dy;
}
}
})
複製代碼
方法也很簡單,就是把data
和methods
展開,放在同一個對象options
中,當咱們經過options.moveBy()
的方式調用moveBy
的時候,moveBy
的this
就是這個對象。 功能實現了,那麼如何實現類型安全呢?接下來就須要在makeObject
方法上作一些改動了,重點就是定義參數類型和返回值類型:
// 只考慮傳入makeObject的config參數只包含data和methods兩個參數
// 定義兩個泛型參數D & M來表明它們的類型
type ObjectConfigDesc<D, M> = {
data: D
methods: M
}
function makeObject<D, M>(config: ObjectConfigDesc<D, M>): D & M {
let data = config?.data || {}
let methods = config?.methods || {}
return {
...data,
...methods
} as D & M
}
複製代碼
此時options
對象的類型已是類型安全的了。可是咱們最關心的moveBy
中的this
對象卻仍然會報類型警告,但咱們知道在實際的運行過程當中,moveBy
中的this
對象已經能夠取到x
和y
了,最後一步就是明確告訴TypeScript
這個this
對象的真實類型了,很是簡單,利用ThisType
:
type ObjectConfigDesc<D, M> = {
data: D
methods: M & ThisType<D & M>
}
複製代碼
這時候再看options
的類型提示已是正確的了:
let options: {
x: number;
y: number;
} & {
moveBy(dx: number, dy: number): void;
}
複製代碼
你們能夠在TypeScript Playground
中親手試一試,感覺會更深入一些。 注意:ThisType
僅支持在對象字面量的上下文中使用,在其餘地方使用做用等同於空接口。
截止到這裏,《玩轉TypeScript工具類型》系列總計三篇就所有完成了,寫這個系列其實就是想記錄本身學習過程當中的一些學習思路和感覺,同時經過文章的方式寫下來加深本身的理解,因此若是有任何錯誤的地方歡迎批評指正。
後續會寫一些關於@vue/reactivity
的源碼分享和平常業務開發中的Vue Composition Utilities
的實踐,敬請期待~