现在位置: 首页 > TypeScript 教程 > 正文

TypeScript 条件类型

条件类型(Conditional Types)是 TypeScript 类型系统中最强大的特性之一。

它允许根据条件动态地选择类型,类似于编程语言中的三元表达式。

条件类型使得类型定义更加灵活,是实现高级工具类型的基础。


条件类型工作原理 条件类型语法 T extends U ? X : Y 执行流程 检查 T 能否赋值给 U → 是 → X | 否 → Y 示例解析 示例 1 IsString<string> string extends string? ✓ → true 示例 2 IsString<number> number extends string? ✗ → false 示例 3 ReturnType<fn> fn extends (...args) → R (推导返回类型)

为什么需要条件类型

在实际的 TypeScript 开发中,我们经常需要根据不同的输入类型返回不同的类型。

例如,一个函数可能接受字符串或数字参数,我们需要根据参数类型返回不同的结果类型。

条件类型提供了一种在类型级别进行逻辑判断的能力,使得类型定义更加灵活和强大。

概念说明:条件类型的语法是 T extends U ? X : Y。如果类型 T 可以赋值给类型 U,则返回类型 X,否则返回类型 Y。


基本语法

条件类型使用三元表达式的语法,在类型层面进行条件判断。

这使得我们可以根据输入类型动态地计算返回类型。

实例

// 条件类型语法:T extends U ? X : Y
// 如果 T 是字符串类型,返回 true,否则返回 false
type IsString<T> = T extends string ? true : false;

// 使用条件类型
// string extends string 为 true,所以 A 类型是 true
type A = IsString<string>;
// number extends string 为 false,所以 B 类型是 false
type B = IsString<number>;

// 使用这些类型
var a: A = true;
var b: B = false;

console.log("string 是字符串?: " + a);
console.log("number 是字符串?: " + b);

运行结果:

string 是字符串?: true
number 是字符串?: false

说明:条件类型会在类型检查时自动进行求值,生成具体的类型。


实际应用:类型过滤

条件类型最常见的应用之一是过滤类型。

例如,可以创建一个类型来排除 null 和 undefined。

实例

// 使用条件类型实现 NonNullable
// 如果 T 是 null 或 undefined,返回 never(空类型),否则返回 T 本身
type NonNullable<T> = T extends null | undefined ? never : T;

// 使用 NonNullable 类型
// string 不是 null/undefined,所以类型是 string
type A = NonNullable<string>;
// null 是 null/undefined,所以类型是 never
type B = NonNullable<null>;
// undefined 是 null/undefined,所以类型是 never
type C = NonNullable<undefined>;

// 验证类型
var a: A = "hello";
console.log("非空: " + a);

运行结果:

非空: hello

never 类型:never 表示永不存在的类型。当条件不满足时,TypeScript 使用 never 来表示"不可用"的类型。


类型推导:infer 关键字

infer 关键字是条件类型中最强大的特性之一。

它允许从类型中"提取"或"推导"出特定的类型部分。

实例

// 使用 infer 推导函数的返回类型
// 如果 T 是函数类型,返回推断出的返回类型 R,否则返回 never
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 定义一个返回用户对象的函数
function getUser() {
    return { name: "Alice" };
}

// 定义一个返回数字的函数
function getNumber() {
    return 42;
}

// 使用 ReturnType 获取函数返回类型
// 推导为 { name: string }
type R1 = ReturnType<typeof getUser>;
// 推导为 number
type R2 = ReturnType<typeof getNumber>;

// 使用推导出的类型
var r1: R1 = { name: "Bob" };
var r2: R2 = 100;

console.log("用户: " + JSON.stringify(r1));
console.log("数字: " + r2);

运行结果:

用户: {"name":"Bob"}
数字: 100

infer 的作用:infer 就像类型系统中的"变量",它可以捕获类型中的特定部分并在结果类型中使用。


分布条件类型

当条件类型的泛型参数是联合类型时,会自动进行"分布"处理。

也就是说,条件会对联合类型中的每个成员分别执行,然后合并结果。

实例

// ToArray 会将类型 T 转换为数组类型
// 当 T 是联合类型时,会自动分布处理每个类型
type ToArray<T> = T extends any ? T[] : never;

// 联合类型会自动分布
// string | number 会分布为:ToArray<string> | ToArray<number>
// 即:string[] | number[]
type StrOrNum = ToArray<string | number>;

// 可以赋值 string[] 或 number[]
var arr: StrOrNum = ["hello"];
// 也可以赋值 number
arr = 42;

console.log("数组: " + arr);

运行结果:

数组: 42

分布机制:条件类型的分布特性是自动开启的。如果想禁用分布,可以使用方括号:[T] extends U


条件类型与映射类型结合

条件类型可以与映射类型结合,创建强大的类型转换工具。

这种组合是实现 TypeScript 内置工具类型的基础。

实例

// 定义用户接口
interface User {
    // 用户 ID
    id: number;
    // 用户名
    name: string;
    // 用户邮箱
    email: string;
}

// 使用映射类型和条件类型实现 Partial
// 遍历 T 的所有属性,添加可选修饰符 ?
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// 使用映射类型和条件类型实现 Required
// 遍历 T 的所有属性,移除可选修饰符 -?
type Required<T> = {
    [P in keyof T]-?: T[P];
};

// 使用 Partial:所有属性变为可选
var partial: Partial<User> = { name: "Alice" };

// 使用 Required:将 Partial<User> 的属性变为必填
// 需要先有 Partial<User> 类型
type RequiredUser = Required<Partial<User>>;
var required: RequiredUser = { name: "Bob", id: 1 };

console.log("可选: " + JSON.stringify(partial));
console.log("必填: " + JSON.stringify(required));

运行结果:

可选: {"name":"Alice"}
必填: {"name":"Bob","id":1}

组合使用:条件类型和映射类型的组合可以实现各种复杂的类型转换,如 Partial、Required、Readonly 等内置工具类型。


高级示例:类型检查

条件类型还可以用于实现更复杂的类型检查。

下面是一些实际开发中有用的类型检查示例。

实例

// 检查类型是否为 any
// any 与任何类型交叉都会得到 any,0 extends any 为 true
type IsAny<T> = 0 extends (1 & T) ? true : false;

// 测试 IsAny
// any 是特殊类型,与任何类型交叉都返回 any
type A = IsAny<any>;       // true
// string 不是 any
type B = IsAny<string>;    // false

console.log("any 是 any?: " + A);
console.log("string 是 any?: " + B);

// 检查类型是否可以赋值
// 如果 T 可以赋值给 U,返回 true,否则返回 false
type IsAssignableTo<T, U> = T extends U ? true : false;

// 测试类型可赋值性
// string 可以赋值给 any,所以返回 true
type CanAssign = IsAssignableTo<string, any>;
console.log("string 赋值给 any?: " + CanAssign);

运行结果:

any 是 any?: true
string 是 any?: true

注意:any 是 TypeScript 中最"宽松"的类型,它可以赋值给任何类型,也可以接收任何类型的赋值。


注意事项

  • 延迟求值:条件类型是延迟求值的,只有在使用具体类型时才会进行计算
  • 分布特性:联合类型会自动触发条件类型的分布机制
  • infer 只能用在 extends 条件中:这是 infer 唯一可以出现的位置
  • 配合映射类型:条件类型与映射类型结合可以创建强大的工具类型

进阶:许多 TypeScript 内置工具类型(如 Partial、Required、Extract)都是用条件类型实现的。


总结

条件类型是 TypeScript 类型系统中最强大的特性之一。

  • 基本语法:T extends U ? X : Y
  • infer 关键字:从类型中推导特定部分
  • 分布特性:联合类型自动分布处理
  • 应用场景:类型过滤、类型推导、类型检查
  • 工具类型:大多数内置工具类型基于条件类型实现

最佳实践:熟练掌握条件类型可以让你写出更加灵活和类型安全的代码。