文章目录
TypeScript 是 JavaScript 的超集,提供了静态类型检查和其他特性,以提高代码的可读性和可维护性。其中,条件类型是一个强大的功能,可以根据输入的类型动态返回不同的类型。本文将详细探讨 TypeScript 中的条件类型,包括其语法、用法及实际应用场景。
一、条件类型概述
1. 什么是条件类型?
条件类型的语法类似于 JavaScript 的条件表达式,采用如下形式:
SomeType extends OtherType ? TrueType : FalseType;
当 SomeType
可以赋值给 OtherType
时,条件类型将返回 TrueType
,否则返回 FalseType
。条件类型能够根据类型之间的关系,灵活地推导出所需的类型。
2. 基本示例
让我们通过几个简单的示例来理解条件类型的工作原理:
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string; // Example1 为 number
type Example2 = RegExp extends Animal ? number : string; // Example2 为 string
在上面的示例中,Example1
返回 number
,因为 Dog
确实扩展自 Animal
;而 Example2
返回 string
,因为 RegExp
并不扩展自 Animal
。
二、条件类型的实际应用
条件类型的真正威力在于与泛型结合使用。通过条件类型,我们可以编写更简洁、可复用的代码。以下是一个使用条件类型的函数示例:
1. 创建标签的函数
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}
上面的函数使用了重载来处理不同类型的输入。虽然这种方式可以工作,但当可处理的类型增多时,重载数量会迅速增加。
2. 简化重载
我们可以使用条件类型来简化这个函数的重载:
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
在这个例子中,我们使用了条件类型 NameOrId
,根据输入类型 T
的不同返回不同的类型。这样,我们就可以在一个函数中处理不同的输入类型,而无需创建多个重载。

3. 条件类型约束
条件类型不仅可以用于返回不同的类型,还可以用于约束泛型,提供更多的类型信息。例如:
type MessageOf<T> = T["message"]; // 此时会报错,因为 T 不确定是否有 message 属性
为了解决这个问题,我们可以添加一个约束:
type MessageOf<T extends {
message: unknown }> = T["message"];
这样,TypeScript 就能够正确推导出 T
中的 message
属性。
4. 处理缺少属性的情况
如果我们希望处理任意类型并在没有 message
属性时返回 never
,我们可以进一步扩展:
type MessageOf<T> = T extends {
message: unknown } ? T["message"] : never;
这样,我们在使用时可以处理缺失 message
属性的情况:
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
type EmailMessageContents = MessageOf<Email>; // EmailMessageContents 为 string
type DogMessageContents = MessageOf<Dog>; // DogMessageContents 为 never
三、推导与推断
1. 使用推断提取类型
条件类型允许我们使用 infer
关键字从类型中推断出子类型。例如,我们可以编写一个类型 Flatten
来扁平化数组类型:
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
在这里,我们使用 infer
关键字推导出 Type
的元素类型,而不是手动使用索引访问。这使得代码更加简洁和易于维护。
2. 提取函数的返回类型
我们还可以编写一个类型来提取函数的返回类型:
type GetReturnType<Type> = Type extends (...args: any[]) => infer Return ? Return : never;
这样,我们就能轻松提取出不同函数的返回类型:
type Num = GetReturnType<() => number>; // Num 为 number
type Str = GetReturnType<(x: string) => string>; // Str 为 string
四、分发条件类型
1. 条件类型的分发行为
当条件类型作用于一个泛型类型时,它会对联合类型进行分发。来看一个例子:
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // StrArrOrNumArr 为 string[] | number[]
在这个例子中,ToArray
将 string | number
作为输入,分别对每个成员类型应用条件类型,返回 string[] | number[]
。
2. 避免分发行为
如果希望避免这种分发行为,可以使用方括号将 extends
关键字包裹起来:
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type ArrOfStrOrNum = ToArrayNonDist<string | number>; // ArrOfStrOrNum 为 (string | number)[]
五、总结
条件类型是 TypeScript 中一个非常强大的特性,它允许开发者根据输入类型动态推导输出类型,极大提高了代码的灵活性和可读性。通过结合泛型、推导和推断,条件类型能够帮助我们构建出更加健壮和可复用的代码。希望本文能够帮助你更好地理解和应用 TypeScript 中的条件类型,使你的开发工作更加高效!
推荐: