js css html

第十三节:TypeScript 条件类型

2022-04-16  本文已影响0人  时光如剑

条件类型

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)[]
上一篇下一篇

猜你喜欢

热点阅读