TypeScript 条件类型
什么是条件类型?
条件类型是 TypeScript 中的一种高级类型,它允许我们根据某个条件来动态选择类型。简单来说,条件类型会根据一个类型表达式的结果,决定最终的类型。它的语法类似于 JavaScript 中的三元运算符,但用于类型系统。
条件类型的基本语法如下:
T extends U ? X : Y
T
是一个类型。U
是另一个类型。- 如果
T
可以赋值给U
,则条件类型的结果是X
,否则是Y
。
示例:简单的条件类型
让我们从一个简单的例子开始:
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. 提取数组元素的类型
假设我们有一个数组类型,我们想要提取数组中元素的类型。可以使用条件类型来实现:
type ExtractElementType<T> = T extends (infer U)[] ? U : never;
type ElementType = ExtractElementType<number[]>; // number
type ElementType2 = ExtractElementType<string[]>; // string
在这个例子中,ExtractElementType
类型会检查 T
是否是一个数组类型。如果是,则提取数组元素的类型 U
,否则返回 never
。
2. 过滤特定类型
我们可以使用条件类型来过滤掉某些类型。例如,过滤掉 null
和 undefined
:
type NonNullable<T> = T extends null | undefined ? never : T;
type Result1 = NonNullable<string | null>; // string
type Result2 = NonNullable<number | undefined>; // number
NonNullable
类型会检查 T
是否是 null
或 undefined
。如果是,则返回 never
,否则返回 T
。
3. 递归条件类型
条件类型还可以用于递归地处理嵌套类型。例如,我们可以定义一个类型来递归地将所有属性变为可选:
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 会将条件类型“分发”到联合类型的每个成员上。例如:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
在这个例子中,ToArray
类型会将联合类型 string | number
分发为 string[] | number[]
。
2. 条件类型与 infer
关键字
infer
关键字可以在条件类型中用于推断类型。例如,我们可以使用 infer
来提取函数返回值的类型:
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 的响应动态地选择类型。我们可以使用条件类型来实现这一点:
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
属性的值动态选择 data
或 error
类型。
总结
条件类型是 TypeScript 中非常强大的工具,它允许我们根据类型条件动态选择类型。通过条件类型,我们可以编写更加灵活和可维护的类型定义。无论是提取数组元素的类型、过滤特定类型,还是递归地处理嵌套类型,条件类型都能帮助我们实现这些功能。
如果你对条件类型感到困惑,建议从简单的例子开始,逐步深入理解其工作原理。多动手实践,尝试编写自己的条件类型,这将帮助你更好地掌握这一概念。
附加资源
练习
- 编写一个条件类型
IsNumber<T>
,检查T
是否是number
类型。 - 使用条件类型和
infer
关键字,提取 Promise 的返回值类型。 - 创建一个递归条件类型,将对象的所有属性变为只读。
通过完成这些练习,你将更深入地理解条件类型的使用场景和技巧。