条件类型 -- Typescript类型编程篇(4)

5,631 阅读3分钟

条件类型是ts中非常强大的功能,形如:T extends U ? X : Y,表示如果TU的子类,则返回类型为X,否则为Y。和三元表达式相似。

我们可以定义一个泛型类型IsString,根据T的类型,判断返回的具体类型是true还是false:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

分布式条件类型

条件类型还有一个特性:分布式条件类型。在结合联合类型使用时(只针对extends左边的联合类型),分布式条件类型会被自动分发成联合类型。

分布式条件类型 等价于
string extends T ? A : B string extends T ? A : B
(string | number) extends T ? A : B (string extends T ? A : B) | (number extends T ? A : B)
(string | number | boolean) extends T ? A : B (string extends T ? A : B) | (number extends T ? A : B) | (boolean extends T ? A : B)

假如我们有一个泛型类型Without<T, U>,用于筛选在T中但不在U中的类型:

type Without<T, U> = T extends U ? never : T;

我们可这样使用:

type A = Without<boolean | number | string, boolean>; // number | string

得到的结果是number | string。让我们一步一步分析:

  1. 给泛型指定具体类型:
type A = Without<boolean | number | string, boolean>;   
  1. 自动分发为联合类型:
type A = Without<boolean, boolean>
          | Without<number, boolean>
          | Without<string, boolean>
  1. 替换为具体的定义:
 type A = (boolean extends boolean ? never : boolean)
             | (number extends boolean ? never : number)
             | (string extends boolean ? never : string)
  1. 计算结果:
 type A = never
             | number
             | string
  1. 简化结果,过滤never:
 type A = number | string

在官方文档中提到,分布式条件类型是有前提的。条件类型中待检查的类型(即extends左边的类型)必须是裸类型(naked type parameter。即没有被诸如数组,元组或者函数包裹。

// naked type
type NakedType<T> = T extends boolean ? "yes" : "no";
type DistributedUsage = NakedType<number | boolean>; // "yes" | "no"

// wrapped type
type WrappedType<T> = Array<T> extends Array<boolean> ? "yes" : "no";
type NonDistributedUsage = WrappedType<number | boolean>; // "no"

infer

条件类型还可以在条件判断中声明泛型类型。通过使用infer关键字。

假如我们希望获取一个函数的返回值:

type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : T;

type Foo = FunctionReturnType<() => void>;  // void
type Bar = FunctionReturnType<(name: string) => string>; // string
type Buz = FunctionReturnType<(name: string, other: string) => boolean>; // boolean

infer R就是在条件判断中声明的新的泛型,它会根据实际传入的泛型类型推断出该泛型的具体类型。

多个相同泛型变量在协变的地方会被推断为联合类型,在逆变的地方会被推断为交叉类型。协变和逆变章节对协变逆变有具体讲解。

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T10 = Foo<{ a: string; b: string }>; // string
type T11 = Foo<{ a: string; b: number }>; // string | number
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U
  : never;
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T21 = Bar<{
  a: (x: { name: string }) => void;
  b: (x: { age: number }) => void;
}>; // { name: string;} & { age: number;}

如果我们对函数重载使用infer,则会以最后一个重载类型定义为准:

declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;
type T30 = ReturnType<typeof foo>; // string | number

ReturnType是ts内置的工具类型,和本节中的FunctionReturnType功能一致。