一、概述

条件类型(Conditional Types)是 TypeScript 中的一种高级类型,允许你根据类型之间的关系进行条件判断,并选择不同的类型。条件类型使得 TypeScript 类型系统更加灵活和强大,能够处理复杂的类型逻辑。本文将详细介绍条件类型的各种应用场景,并通过示例代码说明每个概念的使用方法。

二、什么是条件类型

条件类型使用语法 T extends U ? X : Y 来表示,其中 T 是待检查的类型,U 是要比较的类型,X 是当 T 满足 U 时返回的类型,Y 是当 T 不满足 U 时返回的类型。

示例

  1. type IsString<T> = T extends string ? true : false;
  2. type A = IsString<string>; // true
  3. type B = IsString<number>; // false

在上面的例子中,IsString 是一个条件类型,用于判断类型 T 是否是 string

使用场景

  • 类型判断:当需要根据类型关系进行判断并选择不同的类型时,如检查类型是否是某个特定类型。
  • 类型转换:当需要根据条件进行类型转换时,如将某个类型转换为另一个类型。
  1. type IsNumber<T> = T extends number ? "Number" : "NotNumber";
  2. type X = IsNumber<number>; // "Number"
  3. type Y = IsNumber<string>; // "NotNumber"

三、条件类型与泛型

条件类型可以与泛型结合使用,创建更加灵活和通用的类型定义。

示例

  1. type RemoveNull<T> = T extends null | undefined ? never : T;
  2. type NonNullableString = RemoveNull<string | null | undefined>; // string
  3. type NonNullableNumber = RemoveNull<number | null>; // number

在上面的例子中,RemoveNull 是一个条件类型,用于移除类型 T 中的 nullundefined

使用场景

  • 泛型类型过滤:当需要对泛型类型进行过滤时,如移除泛型类型中的某些特定类型。
  • 类型优化:当需要优化类型定义以提高类型安全性时,如移除不必要的类型。
  1. type ExtractArrayType<T> = T extends (infer U)[] ? U : T;
  2. type StringArrayElement = ExtractArrayType<string[]>; // string
  3. type NumberArrayElement = ExtractArrayType<number[]>; // number
  4. type NonArrayElement = ExtractArrayType<string>; // string

四、分布式条件类型

当条件类型作用于联合类型时,会对联合类型的每个成员进行独立的条件判断,这被称为分布式条件类型。

示例

  1. type ToArray<T> = T extends any ? T[] : never;
  2. type StrArr = ToArray<string>; // string[]
  3. type NumArr = ToArray<number>; // number[]
  4. type UnionArr = ToArray<string | number>; // string[] | number[]

在上面的例子中,ToArray 是一个分布式条件类型,将联合类型的每个成员转换为数组类型。

使用场景

  • 联合类型转换:当需要对联合类型的每个成员进行转换时,如将联合类型的每个成员转换为数组类型。
  • 类型操作简化:当需要简化对联合类型的操作时,如批量应用类型转换。
  1. type Flatten<T> = T extends (infer U)[] ? U : T;
  2. type Str = Flatten<string[]>; // string
  3. type Num = Flatten<number[]>; // number
  4. type NonArray = Flatten<string>; // string

五、条件类型的内置类型

TypeScript 提供了一些内置的条件类型,用于处理常见的类型操作。

Exclude<T, U>

Exclude 类型从类型 T 中排除所有可以赋值给类型 U 的类型。

  1. type Excluded = Exclude<string | number | boolean, boolean>; // string | number

Extract<T, U>

Extract 类型从类型 T 中提取所有可以赋值给类型 U 的类型。

  1. type Extracted = Extract<string | number | boolean, boolean>; // boolean

NonNullable<T>

NonNullable 类型移除类型 T 中的 nullundefined

  1. type NonNull = NonNullable<string | number | undefined>; // string | number

ReturnType<T>

ReturnType 类型获取函数类型 T 的返回类型。

  1. function exampleFunction() {
  2. return { name: "Alice", age: 25 };
  3. }
  4. type ExampleReturnType = ReturnType<typeof exampleFunction>; // { name: string; age: number }

InstanceType<T>

InstanceType 类型获取构造函数类型 T 的实例类型。

  1. class ExampleClass {
  2. constructor(public name: string, public age: number) {}
  3. }
  4. type ExampleInstance = InstanceType<typeof ExampleClass>; // ExampleClass

六、自定义条件类型

你还可以创建自己的条件类型,以满足特定的需求。

示例:将联合类型转换为交叉类型

  1. type UnionToIntersection<U> = (U extends any ? (x: U) => any : never) extends (x: infer R) => any ? R : never;
  2. type U = { a: string } | { b: number } | { c: boolean };
  3. type I = UnionToIntersection<U>; // { a: string } & { b: number } & { c: boolean }

使用场景

  • 类型转换:当需要将联合类型转换为交叉类型时,如将多个类型的属性合并到一个新类型中。
  • 复杂类型操作:当需要进行复杂的类型操作时,如自定义类型逻辑。
  1. type IsNever<T> = [T] extends [never] ? true : false;
  2. type A = IsNever<never>; // true
  3. type B = IsNever<string>; // false

结论

条件类型是 TypeScript 中的重要特性,提供了灵活且强大的类型判断和转换方式。通过掌握条件类型与泛型、分布式条件类型、内置条件类型以及自定义条件类型的结合使用,你可以编写出更具通用性和可重用性的代码。在实际开发中,合理使用条件类型可以提高代码的类型安全性和可读性,帮助你更好地处理复杂的数据结构和类型操作。