Skip to content

条件类型

在 TypeScript 中,条件类型(Conditional Types)是一种高级类型,它允许根据类型检查中的条件来选择不同的类型。条件类型通常用于泛型编程中,可以根据输入的类型参数进行条件判断,并输出不同的类型。

基本语法

typescript
T extends U ? X : Y
// TypeA extends TypeB ? Result1 : Result2;

其中,T 是待检查的类型,U 是要与 T 进行比较的类型,XY 分别是条件成立和不成立时的返回类型。 需要注意的是,条件类型中使用 extends 判断类型的兼容性,而非判断类型的全等性。这是因为在类型层面中,对于能够进行赋值操作的两个变量,我们并不需要它们的类型完全相等,只需要具有兼容性,而两个完全相同的类型,其 extends 自然也是成立的

eg.

typescript
type NonNullable<T> = T extends null | undefined ? never : T

type StrOrNum = string | number
type NonNullStrOrNum = NonNullable<StrOrNum> // 结果为 string | number

NonNullable<T> 是一个条件类型,它用于移除 T 类型中的 nullundefined。如果 T 类型是 nullundefined,则返回 never 类型,否则返回 T 类型本身。

分布式条件类型

分布式条件类型是指条件类型应用到联合类型时,会进行分发(Distributive)处理。当条件类型应用到联合类型时,会分别对联合类型的每个成员进行条件判断,最后将结果联合起来。

eg.1

typescript
type Condition<T> = T extends 1 | 2 | 3 ? T : never

// 1 | 2 | 3;分别带入 12345
// 1 extends 1 | 2 | 3  => 1
// 2 extends 1 | 2 | 3  => 2
// 3 extends 1 | 2 | 3  => 3
// 4 extends 1 | 2 | 3  => never
// 5 extends 1 | 2 | 3  => never
// 最后结果做联合,得到 1 | 2 | 3
type Res1 = Condition<1 | 2 | 3 | 4 | 5>

// never  Res2 并不是分布式的,注意!这里是直接判定
type Res2 = 1 | 2 extends 1 | 2 | 3 ? true : never

上面示例中,Res2 并不是分布式条件类型,Res1 是, 通过 Condition<T>, T 传入了一个联合类型。

eg.2

typescript
type Naked<T> = T extends boolean ? 'Y' : 'N'
type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N'

// "N" | "Y"
type Res3 = Naked<number | boolean>

// "N"
type Res4 = Wrapped<number | boolean>

针对 Res3,相当于执行下面的判断

typescript
type Naked<T> = T extends boolean ? 'Y' : 'N'

// (number extends boolean ? "Y" : "N") | (boolean extends boolean ? "Y" : "N")
// "N" | "Y"
type Res3 = Naked<number | boolean>

对于 Res4,泛型参数 <number | boolean>,传入 Wrapped 后被[]包裹了,即[T]

typescript
type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N'

在条件类型 Wrapped<T> 中,条件是 [T] extends [boolean],这里并不是分布式条件类型。因为 [T]** 和 **[boolean] 都是具体的数组类型,而不是联合类型。因此,它们之间不会进行分布式处理。 执行逻辑

  1. 对于 Wrapped<number | boolean>T 被推断为 number | boolean
  2. 根据条件类型 Wrapped<T> 中的条件 [T] extends [boolean],我们可以看到数组 [number | boolean] 并不等同于 [boolean],因此条件不成立。
  3. 根据条件类型的逻辑,如果条件不成立,则返回 'N'。

注意!

分布式条件类型的条件是:

  1. 类型参数需要是一个联合类型 。
  2. 类型参数需要通过泛型参数的方式传入,而不能直接进行条件类型判断。
  3. 条件类型中的泛型参数不能被包裹

两个泛型参数

typescript
type Diff<T, U> = T extends U ? never : T
type Filter<T, U> = T extends U ? T : never

// 结果为 "c"
type MyType = Diff<'a' | 'b' | 'c', 'a' | 'b' | 'd'>
// 结果为 "a" | "b"
type MyType2 = Filter<'a' | 'b' | 'c', 'a' | 'b' | 'd'>

Diff<T, U>Filter<T, U> 是条件类型,它们被应用到联合类型 **"a" | "b" | "c"**和 **"a" | "b" | "d"**上,分别对每个成员进行条件判断,最后将结果联合起来。

对于 type Diff<T, U> = T extends U ? never : T 中,如果 T 是联合类型,U 也是联合类型,怎么拆分的呢,主要看 T extends U, 这么写,表示,把 T 中的每个类型拆分,看是否满足 U。**extends 后面跟的那个U,不需要拆分**,只拆前面的 T。

我们改一下位置 条件判断中 T, U 位置

typescript
// 改为 U extends T
type Diff<T, U> = U extends T ? never : T
type MyType = Diff<'a' | 'b' | 'c', 'a' | 'b' | 'd'>

对于给定的类型 T'a' | 'b' | 'c',类型 U'a' | 'b' | 'd'

  • 对于类型 U 中的每个成员 'a''b''d'
    • 'a'T 中存在,因此是 T 的子类型。
    • 'b'T 中存在,因此是 T 的子类型。
    • 'd'T 中不存在,因此不是 T 的子类型。返回 T
    • 最终的结果是 'a' | 'b' | 'c'

infer 关键字

定义

infer 关键字用于从泛型类型中推断(infer)出一个新的类型。通常情况下,infer 关键字用于条件类型(Conditional Types)中,允许我们从泛型类型中提取出具体的类型,并将其用于条件判断。 infer,意为推断,如 infer R R 就表示 待推断的类型

语法

infer 关键字通常与条件类型一起使用,语法如下:

typescript
type ExtractType<T> = T extends SomeGenericType<infer U> ? U : DefaultType

infer U 表示我们希望从 SomeGenericType<T> 中推断出的类型,并将其赋值给变量 U

示例

eg.1

typescript
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T

// 符合元组结构,首尾元素替换[2, 1]

// [1, 2] 作为泛型参数T传入
// [1, 2] extends [infer A, infer B],A推断为1,B推断为2
// 1 => A , 2 => B ; 返回[2, 1]
type SwapResult1 = Swap<[1, 2]>
// 不符合结构,没有发生替换,仍是 [1, 2, 3]
type SwapResult2 = Swap<[1, 2, 3]>

eg.2

typescript
type Params<T> = T extends (...args: infer Args) => any ? Args : never

function foo(x: number, y: string): void {}

type FooParams = Params<typeof foo> // 结果为 [number, string]

eg.3 从 Promise 类型中提取 resolved 值类型

typescript
type PromiseValue<T> = T extends Promise<infer V> ? V : T

type PromiseValueResult1 = PromiseValue<Promise<number>> // number
type PromiseValueResult2 = PromiseValue<number> // number,但并没有发生提取
type PromiseValueResult3 = PromiseValue<Promise<Promise<boolean>>> // Promise<boolean>,只提取了一层

如果要 PromiseValueResult3 提取 boolean 类型,则需要嵌套的提取了

typescript
type PromiseValue<T> = T extends Promise<infer V>
  ? V extends Promise<infer N>
    ? N
    : V
  : T

可以使用递归处理任意嵌套深度提取

typescript
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T

MD 文件 Wrapped<number | boolean> 带尖括号的需要用代码块包裹起来,不然编译报错