# 七、条件类型

Ts中也存在这种类似三目运算的条件判断,用来帮助我们描述输入类型和输出类型之间的关系。

interface Animal {
  live(): void;
}

interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string;     
// type Example1 = number
 
type Example2 = RegExp extends Animal ? number : string;   
// type Example2 = string

条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):

# 条件类型约束 (Conditional Type Constraints)

通常,使用条件类型会为我们提供一些新的信息。正如使用 类型保护(type guards) 可以 收窄类型(narrowing) 为我们提供一个更加具体的类型,条件类型的 true 分支也会进一步约束泛型,举个例子:

// 我们想要获取指定类型下的message的类型
type MessageOf<T> = T["message"];
// Type '"message"' cannot be used to index type 'T'.

TypeScript 报错是因为 T 不知道有一个名为 message 的属性。我们可以约束 T,这样 TypeScript 就不会再报错:

type MessageOf<T extends { message: unknown }> = T["message"];
 
interface Email {
  message: string;
}
 
type EmailMessageContents = MessageOf<Email>;
// type EmailMessageContents = string

但是,如果我们想要 MessgeOf 可以传入任何类型,但是当传入的值没有 message 属性的时候,则返回默认类型比如 never 呢?

我们可以把约束移出来,然后使用一个条件类型:

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
 
interface Email {
  message: string;
}
 
interface Dog {
  bark(): void;
}
 
type EmailMessageContents = MessageOf<Email>;           
// type EmailMessageContents = string
 
type DogMessageContents = MessageOf<Dog>;          
// type DogMessageContents = never

在 true 分支里,TypeScript 会知道 T 有一个 message属性。

再举一个例子,我们写一个 Flatten 类型,用于获取数组元素的类型,当传入的不是数组,则直接返回传入的类型:

type Flatten<T> = T extends any[] ? T[number] : T;
 
// Extracts out the element type.
type Str = Flatten<string[]>;  
// type Str = string
 
// Leaves the type alone.
type Num = Flatten<number>;  
// type Num = number

注意这里使用了索引访问类型 (opens new window)里的 number 索引,用于获取数组元素的类型。

# 在条件类型里推断(Inferring Within Conditional Types)

条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果。借助 infer,我们修改下 Flatten 的实现,不再借助索引访问类型“手动”的获取出来:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

我们也可以使用 infer 关键字写一些有用的 类型帮助别名(helper type aliases)。举个例子,我们可以获取一个函数返回的类型:

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;
 
type Num = GetReturnType<() => number>;
// type Num = number
 
type Str = GetReturnType<(x: string) => string>;
// type Str = string
 
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;   
// type Bools = boolean[]
Last Updated: 12/20/2022, 11:11:03 PM