【TypeScript】深入条件类型Ts中的条件类型乍一看似乎令人困惑,但它基本上只是在某些特定情况下简化我们编写类型方
Jym好😘,我是珑墨,今天给大家分享 【TypeScript】深入条件类型,嘎嘎的😍,看下面。
TypeScript 条件类型是个高级类型,它允许你根据类型信息来定义类型。我们可以根据不同的条件返回不同的类型,从而实现更灵活和精确的类型控制。 👀先看下条件类型的几个关键点和使用方法:
基本语法
条件类型的基本形式是 T extends U ? X : Y
,其中:
T
是要检查的类型。U
是T
需要满足的条件类型。X
是当T
扩展(或兼容)U
时返回的类型。Y
是当T
不满足U
的条件时返回的类型。
简单例子
假设我们想定义一个类型,它根据传入的是字符串还是数字来返回不同的类型:
type StringOrNumberToBoolean<T> = T extends string ? boolean : number;
let result1: StringOrNumberToBoolean<string>; // result1 类型为 boolean
let result2: StringOrNumberToBoolean<number>; // result2 类型为 number
配合使用infer
infer
关键字用于从泛型参数中推断类型。这对于更复杂的条件类型非常有用:
type ExtractString<T> = T extends infer U ? U extends string ? U : never : never;
let extracted: ExtractString<[number, string, boolean]>; // extracted 类型为 string
实用的内置条件类型
Ts 提供了一些内置的条件类型,如 Extract
, Exclude
, NonNullable
, ReturnType
等,它们都是基于条件类型构建的。
Extract<T, U>
:从T
中提取可以分配给U
的类型。Exclude<T, U>
:从T
中排除可以分配给U
的类型。NonNullable<T>
:从T
中排除null
和undefined
。ReturnType<T>
:获取函数返回类型。
应用场景
条件类型常用于:
- 泛型约束,使泛型更加灵活和强大。
- 类型守卫,尤其是配合用户自定义的类型保护函数。
- 创建复杂的类型映射,比如将类的属性类型映射到一个新的类型结构中。
😎热身结束,深入操作下吧!!
条件类型在Ts中的工作原理
让我们看一个简单的例子来理解它是如何工作的。在这里,值可以是用户的出生日期 (DOB) 或年龄。如果是出生日期,则类型应为字符串,但如果是年龄,则应为数字。我们将定义三种类型:Dob
Age
UserAgeInformation
type Dob = string;
type Age = number;
type UserAgeInformation<T> = T extends number ? number : string;
type UserAgeInformation<T> = T extends number ? number : string;
我们可以在这里传递任何类型。然后我们说,如果number ,则类型为number 。否则string。T
UserAgeInformation
T extends number
number
string
T
number
UserAgeInformation
number
然后,如果我们希望它是一个数字,我们可以传入 in,如果我们希望它是一个字符串,我们可以传入:Age
userAgeInformation
Dob
type Dob = string;
type Age = number;
type UserAgeInformation<T> = T extends number ? number : string;
let userAge:UserAgeInformation<Age> = 100;
let userDob:UserAgeInformation<Dob> = '12/12/1945';
将条件类型与 keyof 组合在一起
我们可以通过检查是否扩展对象来更进一步。例如,假设我们经营一家企业,该企业有两种类型的客户:s 和 s。虽然 a 有地址,但 a 通常只有一个位置。对于每个地址,我们都有不同的地址格式,如下所示:T
Horse``User
User
Horse
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
UserAddress
HorseAddress
在条件返回中使用T
我们甚至可以在条件回报中使用它自己。在此示例中,由于被定义为当我们调用它时,是 类型,并且需要以该类型定义的字段: T
User
UserType<User>
myUser
User
age
name
address
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 时的并集类型
如果我们要在这里传递一个联合类型,则每个类型都将单独测试。例如,假设我们执行了以下操作:
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
}
type UserType<T> = T extends { name: string } ? T[] : never;
// ^ -- will return the type arguement T as T[], if T contains the property `name` of type `string`
let myUser:UserType<User | Horse> = [{ name: "John" }, { name: "Horse" }]
// ^ -- becomes User[] | Horse[], since both User and Horse have the property name
在这里,我们简化了,并且只具有所需的属性。在我们的条件类型中,这两种类型都包含属性 。因此,两者都返回 true,返回的类型为 。由于两者都返回 true,并且类型为 ,因此我们可以简单地提供包含 name 属性的对象数组。User
Horse
name
name
T[]
myUser
User[] | Horse[]
此行为通常没问题,但在某些情况下,你可能希望改为返回 either or 的数组。在这种情况下,如果我们想避免像这样的类型分布,我们可以在周围添加括号,并且:User
Horse
T
{ name: string }
type User = {
age?: number,
name: string,
address?: string
}
type Horse = {
age?: number,
name: string
}
type UserType<T> = [T] extends [{ name: string }] ? T[] : never;
// ^ -- here, we avoid distributing the types, since T and { name: string } are in brackets
let myUser:UserType<User | Horse> = [{ name: "John" }, { name: "Horse" }]
// ^ -- that means the type is slightly different now - it is (User | 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;
let myItem1:ArrayType<NumberArray> = 45
// ^ -- since the items in `NumberArray` are of type `number`, the type of `myItem` is `number`.
let myItem2:ArrayType<StringArray> = 'string'
// ^ -- since the items in `StringArray` are of type `string`, the type of `myItem` is `string`.
let myItem3:ArrayType<MixedArray> = 'string'
// ^ -- since the items in `MixedArray` can be `string` or `number, the type of `myItem is `string | number`
在这里,我们在条件类型中定义了一个新参数,它是扩展中的项。值得注意的是,这仅在我们传入的类型是数组时才有效,因为我们使用的是 .Item
Array
T
Array<infer Item>
如果 where 是一个数组,则返回其项的类型。如果不是数组,则类型为 。T
ArrayType
T``ArrayType
never
条件类型看似乎令人困惑
Ts中的条件类型乍一看似乎令人困惑,但它基本上只是在某些特定情况下简化我们编写类型方式的另一种方法。了解它是如何工作的,如果你在某个地方的存储库或项目中用,或者对于简化你自己的代码库还是很有用的。
今天就到这儿吧。😂
感谢jym浏览本文,共同进步🤞,若有更好的观点,欢迎评论区讨论哈🌹。
转载自:https://juejin.cn/post/7368320489890824228