TypeScript 条件类型详解:语法、分布与推断

作者:异步漫步时间:2026-10-25 18:59:26浏览:506

本文详解 TypeScript 条件类型的语法与应用。通过三元运算符定义类型映射,结合 keyof 检查对象属性,并深入解析分布类型(Distributive Conditional Types)的行为及避免技巧。最后介绍如何利用 infer 关键字在条件类型中推断数组元素类型,帮助开发者更灵活地处理复杂类型逻辑。

TypeScript 的条件类型使用方式

我们可以利用 TypeScript 中的条件类型,根据逻辑条件来定义特定的类型,其用法类似于编写运行时代码。

其语法结构与 JavaScript 中熟悉的三元运算符一致:condition ? ifConditionTrue : ifConditionFalse。

接下来我们分析其工作原理。

假设存在一个值,它可能表示用户的出生日期,也可能表示年龄。

  • 若该值代表出生日期,其类型应为 string。
  • 若该值代表年龄,其类型则为 number。

下面我们来定义相关的三种类型。

type Dob = string;
type Age = number;
type UserAgeInformation<T> = T extends number ? number : string;

Dob 和 Age 的含义无需多言,重点解释 UserAgeInformation。

该类型接收一个泛型参数,可以是任意类型。如果 T extends number 为 true,即传入类型为 number,则 UserAgeInformation 被定义为 number 类型;否则,它被定义为 string 类型。

在实际使用中,可以如下操作:

let userAge:UserAgeInformation<Age> = 100;
let userDob:UserAgeInformation<Dob> = '12/12/1945';

条件类型和 keyof 组合

除了上述基本用法,我们还可以通过检查类型是否扩展了特定对象来进一步细化。例如,假设存在两种客户类型:Horse 和 User。

这两种客户类型都包含 age 和 name 两个字段。

  • User 类型的客户额外包含一个 address 字段,用于表示详细地址。
  • Horse 类型的客户则包含 location 字段,仅表示大致位置。

我们来定义这些类型:

type User = {
    age: number,
    name: string,
    address: string
}
type Horse = {
    age: number,
    name: string
}
type UserAddress = {
    addressLine1: string,
    city: string,
    country: string,
}
type HorseAddress = {
    location: 'farm' | 'savanna' | 'field' | 'other'
}

未来可能会出现其他类型的客户,因此我们可以通过检查 T 是否具备 address 属性来决定类型:若具备,则使用 UserAddress 类型;否则使用 HorseAddress 类型。

type AddressComponents<T> = T extends { address: string } ? UserAddress : HorseAddress
let userAddress:AddressComponents<User> = {
    addressLine1: "123 Fake Street",
    city: "Boston",
    country: "USA"
}
let horseAddress:AddressComponents<Horse> = {
    location: 'farm'
}

T extends { address: string } 的含义是检查 T 是否具有 address 属性。

在条件返回中使用 T

在三元表达式的条件返回部分,我们也可以直接使用 T。

参考以下示例:

type User = {
    age: number,
    name: string,
    address: string
}
type Horse = {
    age: number,
    name: string
}
type UserType<T> = T extends { address: string } ? T : Horse
let myUser: UserType<User> = {
    age: 104, 
    name: "John Doe",
    address: "123 Fake Street"
}

当 T 被定义为 User 时,调用 UserType 后,myUser 的类型即为 User,且必须包含该类型中定义的字段。

在类型输出中使用 T 时的联合类型

如果我们在此处传递一个联合类型:

type UserType<T> = T extends { address: string } ? T : string
let myUser: UserType<User | Horse> = {
    age: 104, 
    name: "John Doe",
    address: "123 Fake Street"
}

myUser 的类型将变为 User|string。这是因为 User 通过了条件检测,而 Horse 未通过,因此其类型被映射为字符串。

如果我们以某种方式修改 T,例如将其设置为数组,所有 T 的值都会被单独处理(分布)。

type User = {
    age?: number,
    name: string,
    address?: string
}
type Horse = {
    age?: number,
    name: string
}
// 如果 T 包含类型是 string 的 name 属性,就会返回 T[]
type UserType<T> = T extends { name: string } ? T[] : never;
// myUser 的类型是 User[]|Horse[],因为 User 和 Horse 都具有 name 属性
let myUser:UserType<User | Horse> = [{ name: "John" }, { name: "Horse" }]

在此示例中,我们简化了 User 和 Horse,仅保留必需的 name 字段。在条件类型中,两种类型都包含 name。

因此两者都会返回 true,且返回类型为 T[]。由于两者均返回 true,myUser 的类型为 User[]|Horse[],这意味着我们可以简单地提供一个包含 name 属性的对象数组。

这种行为通常符合预期,但在某些情况下,我们希望返回一个单一的数组类型。

在这种情况下,若要避免分布类型,可以在 T 周围添加方括号,即写成 [T]。

type User = {
    age?: number,
    name: string,
    address?: string
}
type Horse = {
    age?: number,
    name: string
}
// 我们避免分布类型,因为 T 和 { name: string} 都在方括号中
type UserType<T> = [T] extends [{ name: string }] ? T[] : never;
// 这样现在的类型不同了,它是 (User|Horse)[]
let myUser:UserType<User | Horse> = [{ name: "John" }, { name: "Horse" }]

通过添加方括号,我们的类型转换为 (User|Horse)[],而不是 User[]|Horse[]。

这在某些特殊场景中非常有用。但需注意,条件类型会增加代码复杂性,不宜滥用。

使用条件类型推断类型

在使用条件类型时,我们还可以使用 infer 关键字。

假设我们有两种类型,分别用于数字数组和字符串数组。

在此示例中,infer 将推断数组中每个项目的类型,并返回正确的类型:

type StringArray = string[]
type NumberArray = number[]
type MixedArray = number[] | string[]
type ArrayType<T> = T extends Array<infer Item> ? Item: never
// 因为 NumberArray 中项目的类型是 number,所以 myItem1 是 number 类型
let myItem1: ArrayType<NumberArray> = 45
// 因为 StringArray 中项目的类型是 string,所以 myItem2 是 string 类型
let myItem2: ArrayType<StringArray> = 'string'
// 因为 MixedArray 中项目的类型是 number|string,所以 myItem3 是 number|string 类型
let myItem3: ArrayType<MixedArray> = 'string'

我们在条件类型中定义了一个新参数 Item,它 extends Array 中的子项 T。

但必须传入数组类型,该逻辑才有效,因为我们使用的是 Array。

如果 T 不是数组,那么 ArrayType 的类型将是 never。

总结

如果你刚接触 TypeScript 的条件类型,可能会感到困惑。但它为解决特定情况下复杂的类型编写提供了一种有效方案。

如果你在项目中看到它,或者希望简化项目代码,它或许非常有用。

以上就是TypeScript 的条件类型使用详解 的详细内容,更多关于TypeScript 条件类型的资料请关注本站其它相关文章!

作者最新文章
相关文章
更多