條件類型
1. 條件類型
條件類型有助于描述輸入和輸出類型之間的關(guān)系
1.1 條件類型語法
條件類型就是根據(jù)一個條件表達式來進行類型檢測, 類似于三目運算符
// 語法
T extends U ? X: Y
若 T
是 U
的子類型缰雇,則類型為 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)[]