第十三節(jié):TypeScript 條件類型

條件類型

1. 條件類型

條件類型有助于描述輸入和輸出類型之間的關(guān)系

1.1 條件類型語法

條件類型就是根據(jù)一個條件表達式來進行類型檢測, 類似于三目運算符

// 語法
T extends U ? X: Y

TU 的子類型缰雇,則類型為 X,否則類型為 Y光戈。若無法確定 T 是否為 U 的子類型皆的,則類型為 X | Y灸芳。

例如:

// 接口
interface Person {
  name: string
  age: number
}


// 擴展接口
interface Student extends Person{
  classId: string
}

// 根據(jù)Student是不是繼承Person接口來判斷返回string類型還是number類型 
type Example = Student extends Person ? string : number
// type Example = string

type Example2 = RegExp extends Person ? string : number
// type Example2 = number

示例中,根據(jù)接口Student 是否繼承Person接口,來決定類型別名Example 具體是string 類型還是number類型


如不是判斷類型的子類型,則返回聯(lián)合類型

例如:

type Person<Type> = Type extends boolean ? string : number

type StringOrNumber = Person<any>
// type StringOrNumber = string | number

代碼解釋: 泛型Person在使用時傳入了any, any類型可是任何類型, 如果是boolean, 則返回string類型, 如果不是boolean,則返回number類型, 此時TypeScript就不好抉擇了, 直接返回string | number的聯(lián)合類型


1.2 條件類型與泛型一起使用

條件類型可能不會立即有用, 但是條件類型的強大之處在于他們和泛型一起使用

例如,我們采用以下createLabel功能

// 定義id接口
interface IdLabel{
  id: number
}

// 定義name接口
interface NameLabel {
  name: string
}


// 定義重載更具入?yún)Q定返回類型
function createLabel(id:number):IdLabel;
function createLabel(name:string):NameLabel;
function createLabel(nameOrId:string | number):NameLabel | IdLabel;
function createLabel(nameOrId:string | number):NameLabel | IdLabel{
  if(typeof nameOrId === 'string'){
    return {name:nameOrId}
  }else{
     return {id:nameOrId}
  }
}

createLabel的這些重載描述了一個JavaScript函數(shù), 該函數(shù)根據(jù)其輸入的類型進行選擇

請注意:示例中我們必須創(chuàng)建三個重載, 一個用于確定的每種情況(string 或者 number)類型, 一個用于最一般的情況(string | number)聯(lián)合類型


相反的,我們可以將該邏輯編寫為條件類型

type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel

首先確定的泛型類型T限定必須繼承number | string, 然后在根據(jù)傳入的泛型更詳細(xì)的是否extends繼承number類型

,如果為true 則返回IdLabel接口, 否則返回NameLabel接口


此時我們就可以使用該條件類型將重載簡化為沒有重載的單個函數(shù)

例如:

function createLabel<T extends string | number>(nameOrId:T):NameOrId<T>{
 throw "unimplemented";
}

const namelabel = createLabel('hello')
// const namelabel: NameLabel

const idlabel = createLabel(10)
// const idlabel: IdLabel

const nameidlabel = createLabel(Math.random() > 0.5 ? 'hello' : 42)
// const nameidlabel: NameLabel | IdLabel


2. 條件類型約束

通常, 條件類型的檢查會為我們提供一些新信息, 就像使用類型保護縮小類型范圍, 可以給我們提供一個更具體的類型一樣.

條件類型的真正分支將會進一步限制我們檢查類型的泛型


2.1 泛型繼承約束

例如.讓我們采取以下措施

type MessageOf<T> = T['message']
// 報錯: message 無法用于索引類型T

在例子中, TypeScript出錯的原因在于T類型不知道有一個message的屬性.

因此我們可以通過extends約束以下T類型, 這樣TypeScript就不會在抱怨了

也就是說泛型的傳入必須滿足具有message屬性

例如:

type MessageOf<T extends {message: unknown}> = T['message']

interface Email {
  message: string
}

type EmailMessage = MessageOf<Email>
// type EmailMessage = string


但是,這樣的約束會帶來另外一個問題, 那就是MessageOf類型不能使用沒有message屬性的類型,例如

interface Dog{
  name: string
}

type DogMessage = MessageOf<Dog>
// 報錯: 類型Dog 不滿足約束 {message:unknown}
// 類型Dog 缺少`message`屬性, 但是類型{message:unknown} 需要該屬性

那么如果我們想MessageOf采用任何類型, 并且在message屬性不可用時, 默認(rèn)為never, 那該怎么辦呢


2.2 條件約束

我們可以嘗試移出約束,并使用條件類型來做到這一點

type MessageOf<T> = T extends {message: unknown } ? T['message'] : never;

interface Email {
  message: string
}

interface Dog{
  name: string
}
type EmailMessage = MessageOf<Email>
// type EmailMessage = string

type DogMessage = MessageOf<Dog>
// type DogMessage = never

在條件為true的分支中,TypeScript知道T類型會有一個message屬性, 為false的分支中直接返回never類型


作為另外一個示例, 我們還可以編寫一個名為Flatten的類型別名, 用于將數(shù)組類型展平, 獲取數(shù)組元素的類型,如果不是數(shù)組類型則不理, 傳入什么類型返回什么類型

type Flatten<T> = T extends any[] ? T[number] : T

type Str = Flatten<string[]>
// type Str = string

type Num = Flatten<number>
// type Num = number

示例中, 當(dāng)Flatten 給定一個數(shù)組類型是, 它會通過索引訪問number來獲取string[]的元素類型. 并返回

如果給定的不是數(shù)組類型, 則返回給定的類型


3. 在條件類型中推斷

條件類型為我們提供了一種方法就是使用infer關(guān)鍵字來推斷我們在真實分支中類型,

例如:我們可以推斷數(shù)組元素的類型,而不是像之前一樣使用索引訪問類型, 并手動的獲取 元素類型

type Flatten<T> = T extends Array<infer Item> ? Item : T

在這里,我們使用了infer關(guān)鍵字聲明性地引入了一個新的泛型類型變量Item, 而不是在條件在條件為true的分支中通過索引訪問數(shù)組元素的類型. 這使我們不必考慮如何挖掘和探索我們感興趣的類型結(jié)構(gòu)


也可以使用infer關(guān)鍵字編寫一些有用的輔助類型別名,

例如,對于簡單的情況,我們可以從函數(shù)類型中提取返回類型:

// 獲取函數(shù)返回類型
type GetReturnType<T> = T extends (...arg:never[]) => infer Return ? Return : never


type Num = GetReturnType<() => number>
// type Num = number


type Str = GetReturnType<(x:string) => string>
// type Str = string


type Bool = GetReturnType<(a:boolean, b:boolean) => boolean[]>
// type Bool = boolean[]


4. 分布式條件類型

當(dāng)條件類型作用于泛型時, 它們給定聯(lián)合類型時變得可分配

例如,采用如下措施

type ToArray<Type> = Type extends any ? Type[] : never;

如果我們將聯(lián)合類型插入ToArray侵续,那么條件類型將應(yīng)用于該聯(lián)合的每個成員。

type ToArray<Type> = Type extends any ? Type[] : never;
 
type StrArrOrNumArr = ToArray<string | number>;
// type StrArrOrNumArr = string[] | number[]

這里發(fā)生的情況是StrArrOrNumArr分布在:

  string | number;

并將聯(lián)合的每個成員類型映射到有效的內(nèi)容:

  ToArray<string> | ToArray<number>;

這給我們留下了:

string[] | number[];


通常渊抄,分配性是期望的行為足淆。extends為避免這種行為巢块,您可以用方括號將關(guān)鍵字的每一側(cè)括起來。

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
 
// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;
           
type StrArrOrNumArr = (string | number)[]
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缸浦,一起剝皮案震驚了整個濱河市夕冲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裂逐,老刑警劉巖歹鱼,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卜高,居然都是意外死亡弥姻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門掺涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庭敦,“玉大人,你說我怎么就攤上這事薪缆⊙砹” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵拣帽,是天一觀的道長疼电。 經(jīng)常有香客問我,道長减拭,這世上最難降的妖魔是什么蔽豺? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮拧粪,結(jié)果婚禮上修陡,老公的妹妹穿的比我還像新娘。我一直安慰自己可霎,他們只是感情好魄鸦,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癣朗,像睡著了一般号杏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天盾致,我揣著相機與錄音,去河邊找鬼荣暮。 笑死庭惜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的穗酥。 我是一名探鬼主播护赊,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼砾跃!你這毒婦竟也來了骏啰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抽高,失蹤者是張志新(化名)和其女友劉穎判耕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翘骂,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡壁熄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碳竟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片草丧。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莹桅,靈堂內(nèi)的尸體忽然破棺而出昌执,到底是詐尸還是另有隱情,我是刑警寧澤诈泼,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布懂拾,位于F島的核電站,受9級特大地震影響厂汗,放射性物質(zhì)發(fā)生泄漏委粉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一娶桦、第九天 我趴在偏房一處隱蔽的房頂上張望贾节。 院中可真熱鬧,春花似錦衷畦、人聲如沸栗涂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斤程。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忿墅,已是汗流浹背扁藕。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疚脐,地道東北人亿柑。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像棍弄,于是被迫代替她去往敵國和親望薄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容