第十三节:TypeScript 条件类型

条件类型

1. 条件类型

条件类型有助于描述输入和输出类型之间的关系

1.1 条件类型语法

条件类型就是根据一个条件表达式来进行类型检测, 类似于三目运算符

// 语法
T extends U ? X: Y

TU 的子类型,则类型为 X,否则类型为 Y。若无法确定 T 是否为 U 的子类型,则类型为 X | Y

例如:

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


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

// 根据Student是不是继承Person接口来判断返回string类型还是number类型 
type Example = Student extends Person ? string : number
// type Example = string

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

示例中,根据接口Student 是否继承Person接口,来决定类型别名Example 具体是string 类型还是number类型


如不是判断类型的子类型,则返回联合类型

例如:

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的联合类型


1.2 条件类型与泛型一起使用

条件类型可能不会立即有用, 但是条件类型的强大之处在于他们和泛型一起使用

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

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

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


// 定义重载更具入参决定返回类型
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函数, 该函数根据其输入的类型进行选择

请注意:示例中我们必须创建三个重载, 一个用于确定的每种情况(string 或者 number)类型, 一个用于最一般的情况(string | number)联合类型


相反的,我们可以将该逻辑编写为条件类型

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

首先确定的泛型类型T限定必须继承number | string, 然后在根据传入的泛型更详细的是否extends继承number类型

,如果为true 则返回IdLabel接口, 否则返回NameLabel接口


此时我们就可以使用该条件类型将重载简化为没有重载的单个函数

例如:

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属性不可用时, 默认为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的类型别名, 用于将数组类型展平, 获取数组元素的类型,如果不是数组类型则不理, 传入什么类型返回什么类型

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

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

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

示例中, 当Flatten 给定一个数组类型是, 它会通过索引访问number来获取string[]的元素类型. 并返回

如果给定的不是数组类型, 则返回给定的类型


3. 在条件类型中推断

条件类型为我们提供了一种方法就是使用infer关键字来推断我们在真实分支中类型,

例如:我们可以推断数组元素的类型,而不是像之前一样使用索引访问类型, 并手动的获取 元素类型

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

在这里,我们使用了infer关键字声明性地引入了一个新的泛型类型变量Item, 而不是在条件在条件为true的分支中通过索引访问数组元素的类型. 这使我们不必考虑如何挖掘和探索我们感兴趣的类型结构


也可以使用infer关键字编写一些有用的辅助类型别名,

例如,对于简单的情况,我们可以从函数类型中提取返回类型:

// 获取函数返回类型
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. 分布式条件类型

当条件类型作用于泛型时, 它们给定联合类型时变得可分配

例如,采用如下措施

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

如果我们将联合类型插入ToArray,那么条件类型将应用于该联合的每个成员。

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

这里发生的情况是StrArrOrNumArr分布在:

  string | number;

并将联合的每个成员类型映射到有效的内容:

  ToArray<string> | ToArray<number>;

这给我们留下了:

string[] | number[];


通常,分配性是期望的行为。extends为避免这种行为,您可以用方括号将关键字的每一侧括起来。

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
 
// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;
           
type StrArrOrNumArr = (string | number)[]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容