書接上文琳彩。在上一節(jié)中,我們學(xué)習(xí)了 8 個最常用的內(nèi)置工具類型碧浊。這些工具類型都是對現(xiàn)有類型進行“變形”的工具瘟仿,它們可以改變類型的結(jié)構(gòu)劳较,但不會改變類型本身的值。這些內(nèi)置工具類型臊恋,本質(zhì)上就是類型系統(tǒng)中的“函數(shù)”墓捻,它們接受范型作為參數(shù)砖第,返回一個新的類型。
這一節(jié)放吩,我們繼續(xù)學(xué)習(xí)內(nèi)置工具類型羽杰,主要集中學(xué)習(xí)infer相關(guān)的幾個內(nèi)置工具:
Parameters<T>
:獲取函數(shù)類型 T 的返回參數(shù)列表
如下例所示忽洛,以元祖形式返回函數(shù)參數(shù)列表:
type T1 = Parameters<(s: string, n: string) => void>; // [string, number]
實現(xiàn)用到了我們基礎(chǔ)語法篇里提到的infer
;在這里,我們使用 infer P
來推斷函數(shù)參數(shù)的類型复哆,并將其賦值給 P
腌零。
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
簡單介紹一下:
- 所有函數(shù)類型的基類是
(...args: any) => any
益涧;Parameters 用于函數(shù)類型的參數(shù)類型提取,所以我們給范型(參數(shù))T加一個限制T extends (...args: any) => any
久免,確保 T 是函數(shù)類型,否則類型拋錯 - 類型推斷
infer
只能配合條件判斷extends
使用记舆;所以我們需要冗余地寫一遍類似的代碼T extends (...args: infer P) => any ? P : never
泽腮;這次主要是為了推斷出參數(shù) args 的類型 P衣赶,然后返回該類型。
p.s. 上面實現(xiàn)中兩個 extends
作用不同:第一個是用于類型限制豪筝;第二個是配合infer的條件判斷摘能。大家不要搞混了团搞。
ReturnType<T>
:獲取函數(shù)類型 T 的返回類型
同理ReturnType的實現(xiàn)也和Parameters差不多,只不過把推斷的參數(shù)類型換成了返回類型:
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
我們擴展一下像吻,能不能自己實現(xiàn)一個類型工具复隆,同時返回參數(shù)類型和返回類型呢挽拂?當(dāng)然可以,這就是兩個infer推斷的事台腥,如下所示:
type ParametersAndReturnType<T extends (...args: any) => any> = T extends (
...args: infer P
) => infer R
? { parameters: P; return: R }
: any;
type T2 = ParametersAndReturnType<(a: string) => number>;
// type T2 = {
// parameters: [a: string];
// return: number;
// }
ConstructorParameters<T>
:獲取構(gòu)造函數(shù)類型 T 的參數(shù)類型
上文提到:所有函數(shù)類型的基類型是 (...args: any) => any
黎侈。而所有構(gòu)造函數(shù)類型 T 的基類型是:
new (...args: any) => any
由于js的類(class)事實上是構(gòu)造函數(shù)的一個語法糖闷游,所以我們還需要考慮class。普通class的基類也自然是new (...args: any) => any
扳埂。但是蛛碌,ts多走了一步,支持了抽象類(abstract class)希太,所以又給new (...args: any) => any
找了個基類——abstract new (...args: any) => any
誊辉。最終亡脑,我們判斷T是否是構(gòu)造函數(shù),就成了判斷T是否是abstract new (...args: any) => any
的子類蛙紫。
ConstructorParameters就是提取構(gòu)造函數(shù)的參數(shù)類型坑傅,實現(xiàn)上和Parameters<T>
差不多——一個infer的事:
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
InstanceType<T>
:獲取構(gòu)造函數(shù)類型 T 的實例類型
InstanceType就是獲取構(gòu)造函數(shù)的返回類型唁毒,實現(xiàn)上參考ReturnType<T>
星爪,也很簡單:
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
其他
除了上面提到的幾個,typescript 3.3 內(nèi)置了ThisParameterType<T>
近零、OmitThisParameter<T>
秒赤、ThisType<T>
憎瘸。這些工具其實為了兼容typescript 2.0 版本里的 this
聲明展開的:類型體操中完全用不到幌甘,現(xiàn)實開發(fā)中也應(yīng)盡量避免在函數(shù)里用 this
。這里由于與infer相關(guān)酥诽,我也把它們的實現(xiàn)列一下:
-
ThisParameterType<T>
:提取函數(shù)類型的this參數(shù)的類型皱埠,如果函數(shù)類型沒有this參數(shù)边器,則返回unknowntype ThisParameterType<T> = T extends (this: infer U, ...args: never) => any ? U : unknown;
實現(xiàn)倒不難,就是調(diào)用的時候有點蠢:要在函數(shù)的第一個參數(shù)里聲明this類型恒界,而且還不能簡單調(diào)用該函數(shù)十酣,要配合apply际长、call工育、bind使用。
function foo(this: string) { return this + ':Hello world'; } // type of foo => (this: string) => string type Foo = ThisParameterType<typeof foo>; // string function numberToString(s: Foo) { return foo.call(s); }
-
OmitThisParameter<T>
:移除函數(shù)類型的this
參數(shù)type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T; type omitThis = OmitThisParameter<(this: number, n: number) => void> // (n: number) => void
這個就是移除了this聲明的函數(shù)類型文留,稍微解釋一下:
-
unknown extends ThisParameterType<T>
: 結(jié)合 ThisParameterType 的實現(xiàn)燥翅,我們可以得出蜕提,如果函數(shù)類型沒有this聲明谎势,那么ThisParameterType<T>
直接返回unknown;所以這里就是單純判斷T有沒有this聲明猖毫,如果沒有须喂,直接返回T本身。 -
T extends (...args: infer A) => infer R
: 如果T是函數(shù)類型掷伙,則提取參數(shù)類型A和返回值類型R又兵,反之直接返回類型T本身沛厨。p.s. 這種條件寫法與T extends (this: infer U , ...args: infer A) => infer R
區(qū)別是:會自動忽略this參數(shù)宙地。 -
(...args: A) => R
: 返回一個新的函數(shù)類型,這個函數(shù)類型不再聲明this類型俄烁,其他參數(shù)類型和返回值類型與T相同绸栅。
實現(xiàn)上也挺簡單,就是有兩層條件判斷页屠。以后我們接觸type challenge真題時粹胯,會碰到更多層的情況。不要慌辰企,可以把代碼類似“抽取函數(shù)”(類型嵌套)的形式來重構(gòu)风纠。比如:
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : OmitThisParameterFunc<T>; type OmitThisParameterFunc<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : T;
-
-
ThisType<T>
:非推理類型位置的標(biāo)記早古的設(shè)計:沒啥用,就是個this的標(biāo)記位牢贸,等于空接口,一筆帶過了潜索。
interface ThisType<T> = {};
小結(jié)
這期我們主要講了infer相關(guān)的幾個內(nèi)置工具類型臭增。infer是type challenge中除了extends以外出場頻率最高的一個關(guān)鍵字,它能夠讓我們在類型體操中實現(xiàn)很多看似不可能的功能竹习。我想大家在學(xué)習(xí)過這幾個工具后誊抛,應(yīng)該能對infer有更深的理解。
本文是該系列的第四篇文章整陌,我們回過頭來思考一下type challenge(類型體操)拗窃,解決這類問題到底有多少意義?其實它就是類似于leetcode的代碼訓(xùn)練泌辫。很多人對leetcode嗤之以鼻(我個人還是比較肯定leetcode作為日常代碼訓(xùn)練的意義的随夸,堅持每天一道leetcode),但是在面試中震放,leetcode題目還是占據(jù)著舉足輕重的地位宾毒。類型體操自然沒有達到leetcode這種業(yè)界地位,b不過一到涉及typescript的高級特性考察殿遂,你覺得面試官能問什么問題呢伍俘?我們學(xué)習(xí)一種語言本質(zhì)上是對自己職業(yè)生涯的一項投資邪锌,投資的最大回報就是找到下一份滿意的工作勉躺。即然你已經(jīng)決定學(xué)習(xí)typescript癌瘾,那就務(wù)必掌握好它最核心的部分——類型系統(tǒng)。
文章同步發(fā)布于an-Onion 的 Github饵溅。碼字不易妨退,歡迎點贊。