跳到主要内容

TypeScript 条件类型

什么是条件类型?

条件类型是 TypeScript 中的一种高级类型,它允许我们根据某个条件来动态选择类型。简单来说,条件类型会根据一个类型表达式的结果,决定最终的类型。它的语法类似于 JavaScript 中的三元运算符,但用于类型系统。

条件类型的基本语法如下:

typescript
T extends U ? X : Y
  • T 是一个类型。
  • U 是另一个类型。
  • 如果 T 可以赋值给 U,则条件类型的结果是 X,否则是 Y

示例:简单的条件类型

让我们从一个简单的例子开始:

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

type Result1 = IsString<"hello">; // true
type Result2 = IsString<42>; // false

在这个例子中,IsString 是一个条件类型。它检查泛型参数 T 是否可以赋值给 string 类型。如果可以,则返回 true,否则返回 false

条件类型的实际应用

条件类型在 TypeScript 中非常有用,尤其是在处理泛型和复杂类型逻辑时。以下是一些常见的应用场景。

1. 提取数组元素的类型

假设我们有一个数组类型,我们想要提取数组中元素的类型。可以使用条件类型来实现:

typescript
type ExtractElementType<T> = T extends (infer U)[] ? U : never;

type ElementType = ExtractElementType<number[]>; // number
type ElementType2 = ExtractElementType<string[]>; // string

在这个例子中,ExtractElementType 类型会检查 T 是否是一个数组类型。如果是,则提取数组元素的类型 U,否则返回 never

2. 过滤特定类型

我们可以使用条件类型来过滤掉某些类型。例如,过滤掉 nullundefined

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

type Result1 = NonNullable<string | null>; // string
type Result2 = NonNullable<number | undefined>; // number

NonNullable 类型会检查 T 是否是 nullundefined。如果是,则返回 never,否则返回 T

3. 递归条件类型

条件类型还可以用于递归地处理嵌套类型。例如,我们可以定义一个类型来递归地将所有属性变为可选:

typescript
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface User {
name: string;
address: {
city: string;
zipCode: string;
};
}

type PartialUser = DeepPartial<User>;

在这个例子中,DeepPartial 类型会递归地将所有属性变为可选,包括嵌套的对象属性。

条件类型的进阶用法

1. 分布式条件类型

当条件类型作用于联合类型时,TypeScript 会将条件类型“分发”到联合类型的每个成员上。例如:

typescript
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>; // string[] | number[]

在这个例子中,ToArray 类型会将联合类型 string | number 分发为 string[] | number[]

2. 条件类型与 infer 关键字

infer 关键字可以在条件类型中用于推断类型。例如,我们可以使用 infer 来提取函数返回值的类型:

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

function foo() {
return 42;
}

type FooReturnType = ReturnType<typeof foo>; // number

在这个例子中,ReturnType 类型会推断出函数 T 的返回值类型 R

实际案例:构建一个类型安全的 API 响应类型

假设我们正在构建一个 API 客户端,并且我们希望根据 API 的响应动态地选择类型。我们可以使用条件类型来实现这一点:

typescript
type ApiResponse<T> = {
success: true;
data: T;
} | {
success: false;
error: string;
};

function handleResponse<T>(response: ApiResponse<T>) {
if (response.success) {
console.log("Data:", response.data);
} else {
console.error("Error:", response.error);
}
}

const successResponse: ApiResponse<{ id: number }> = {
success: true,
data: { id: 42 },
};

const errorResponse: ApiResponse<{ id: number }> = {
success: false,
error: "Not found",
};

handleResponse(successResponse); // Data: { id: 42 }
handleResponse(errorResponse); // Error: Not found

在这个例子中,ApiResponse 类型根据 success 属性的值动态选择 dataerror 类型。

总结

条件类型是 TypeScript 中非常强大的工具,它允许我们根据类型条件动态选择类型。通过条件类型,我们可以编写更加灵活和可维护的类型定义。无论是提取数组元素的类型、过滤特定类型,还是递归地处理嵌套类型,条件类型都能帮助我们实现这些功能。

提示

如果你对条件类型感到困惑,建议从简单的例子开始,逐步深入理解其工作原理。多动手实践,尝试编写自己的条件类型,这将帮助你更好地掌握这一概念。

附加资源

练习

  1. 编写一个条件类型 IsNumber<T>,检查 T 是否是 number 类型。
  2. 使用条件类型和 infer 关键字,提取 Promise 的返回值类型。
  3. 创建一个递归条件类型,将对象的所有属性变为只读。

通过完成这些练习,你将更深入地理解条件类型的使用场景和技巧。