条件类型
在 TypeScript 中,条件类型(Conditional Types)是一种高级类型,它允许根据类型检查中的条件来选择不同的类型。条件类型通常用于泛型编程中,可以根据输入的类型参数进行条件判断,并输出不同的类型。
基本语法
T extends U ? X : Y
// TypeA extends TypeB ? Result1 : Result2;
其中,T 是待检查的类型,U 是要与 T 进行比较的类型,X 和 Y 分别是条件成立和不成立时的返回类型。 需要注意的是,条件类型中使用 extends 判断类型的兼容性,而非判断类型的全等性。这是因为在类型层面中,对于能够进行赋值操作的两个变量,我们并不需要它们的类型完全相等,只需要具有兼容性,而两个完全相同的类型,其 extends 自然也是成立的
eg.
type NonNullable<T> = T extends null | undefined ? never : T
type StrOrNum = string | number
type NonNullStrOrNum = NonNullable<StrOrNum> // 结果为 string | number
NonNullable<T>
是一个条件类型,它用于移除 T 类型中的 null 和 undefined。如果 T 类型是 null 或 undefined,则返回 never 类型,否则返回 T 类型本身。
分布式条件类型
分布式条件类型是指条件类型应用到联合类型时,会进行分发(Distributive)处理。当条件类型应用到联合类型时,会分别对联合类型的每个成员进行条件判断,最后将结果联合起来。
eg.1
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
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,相当于执行下面的判断
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]
type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N'
在条件类型 Wrapped<T>
中,条件是 [T] extends [boolean]
,这里并不是分布式条件类型。因为 [T]** 和 **[boolean]
都是具体的数组类型
,而不是联合类型
。因此,它们之间不会进行分布式处理。 执行逻辑
- 对于
Wrapped<number | boolean>
,T 被推断为number | boolean
。 - 根据条件类型
Wrapped<T>
中的条件[T] extends [boolean]
,我们可以看到数组[number | boolean]
并不等同于[boolean]
,因此条件不成立。 - 根据条件类型的逻辑,如果条件不成立,则返回 'N'。
注意!
分布式条件类型的条件是:
- 类型参数需要是一个联合类型 。
- 类型参数需要通过泛型参数的方式传入,而不能直接进行条件类型判断。
- 条件类型中的泛型参数不能被包裹
两个泛型参数
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 位置
// 改为 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 关键字通常与条件类型一起使用,语法如下:
type ExtractType<T> = T extends SomeGenericType<infer U> ? U : DefaultType
infer U
表示我们希望从 SomeGenericType<T>
中推断出的类型,并将其赋值给变量 U
。
示例
eg.1
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
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 值类型
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 类型,则需要嵌套的提取了
type PromiseValue<T> = T extends Promise<infer V>
? V extends Promise<infer N>
? N
: V
: T
可以使用递归处理任意嵌套深度提取
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T
MD 文件
Wrapped<number | boolean>
带尖括号的需要用代码块包裹起来,不然编译报错