// 直接使用联合类型判断,ts会将其视为整体进行判断,而不是一个一个进行判断
type Test = string | number | boolean extends string ? string : never; // never
type Test2 = string extends string | number | boolean ? "yes" : "no"; // 'yes'
type CondType<T> = T extends string ? T : never;
// 可以发现,使用泛型的类型是一个一个进行比较的,只要有一个满足条件,就会返回这个类型
type Test3 = CondType<string | number | boolean>; // string
// 使用条件类型封装通用工具
type CommonType<T> = T extends object ? T : never;
// 所有需要约束的类型都可以使用这个工具进行约束,以后需要修改的话,只需要修改这个工具即可,不需要一个一个去修改
type PersonType = CommonType<Person>; // Person
type StringType = CommonType<string>; // never
type NumberType = CommonType<number>; // never
TypeScript 条件类型是一种强大的类型工具,允许你根据类型的特性来进行类型推断和转换。条件类型的语法类似于 JavaScript 中的三元运算符 condition ? trueType : falseType
。以下是对你提供的代码的详细解释:
代码解释
// 直接使用联合类型判断,ts会将其视为整体进行判断,而不是一个一个进行判断
type Test = string | number | boolean extends string ? string : never; // never
type Test2 = string extends string | number | boolean ? "yes" : "no"; // 'yes'
联合类型整体判断:
type Test = string | number | boolean extends string ? string : never; // never
这里
string | number | boolean extends string
被视为整体进行判断。因为联合类型string | number | boolean
并不完全是string
类型,所以结果是never
。单个类型判断:
type Test2 = string extends string | number | boolean ? "yes" : "no"; // 'yes'
这里
string extends string | number | boolean
是单个类型判断。因为string
是string | number | boolean
的子类型,所以结果是"yes"
。
type CondType<T> = T extends string ? T : never;
// 可以发现,使用泛型的类型是一个一个进行比较的,只要有一个满足条件,就会返回这个类型
type Test3 = CondType<string | number | boolean>; // string
泛型条件类型:
type CondType<T> = T extends string ? T : never;
这里
CondType
是一个泛型条件类型。对于联合类型string | number | boolean
,TypeScript 会逐个进行判断:string extends string ? string : never
->string
number extends string ? number : never
->never
boolean extends string ? boolean : never
->never
所以
CondType<string | number | boolean>
的结果是string
。
// 使用条件类型封装通用工具
type CommonType<T> = T extends object ? T : never;
// 所有需要约束的类型都可以使用这个工具进行约束,以后需要修改的话,只需要修改这个工具即可,不需要一个一个去修改
type PersonType = CommonType<Person>; // Person
type StringType = CommonType<string>; // never
type NumberType = CommonType<number>; // never
封装通用工具:
type CommonType<T> = T extends object ? T : never;
这里
CommonType
是一个通用的条件类型工具,用于约束类型为对象类型。如果T
是对象类型,则返回T
,否则返回never
。CommonType<Person>
->Person
CommonType<string>
->never
CommonType<number>
->never
条件类型的应用
条件类型在 TypeScript 中非常有用,特别是在以下场景中:
类型推断:根据条件推断出不同的类型。
类型转换:将一种类型转换为另一种类型。
类型保护:确保代码在运行时的类型安全。
类型映射:操作对象和数组类型。
示例
以下是一个简单的条件类型示例,用于提取函数的参数类型:
type ParametersType<T> = T extends (...args: infer P) => any ? P : never;
function exampleFunction(a: string, b: number): void {}
type ExampleParameters = ParametersType<typeof exampleFunction>; // [string, number]
通过理解和掌握条件类型,可以编写更强大和灵活的 TypeScript 代码,提高代码的类型安全性和可维护性。
条件类型的复杂应用
在一个接口上自由添加属性组成新的类型,会有这样的需求,我们直接在接口上添加属性,可能会影响其它地方使用,或者这个接口是第三方提供的,这个时候可以应用到条件类型
interface Person {
name: string;
age: number;
location: string;
}
// 添加属性到接口上的工具
type AddProp<T, K extends string, V> = T & { [P in K]: V };
// 添加属性到Person接口上,这样得的是交叉类型,还不符合我们的要求
type PersonWithGender = AddProp<Person, "a", number>;
type AddPropToObj<T, K extends string, V> = {
[P in keyof T | K]: P extends keyof T ? T[P] : V;
};
// 可以发现属性a被添加到了Person接口上
type PersonWithGender2 = AddPropToObj<Person, "a", number>; // { name: string; age: number; location: string; a: number; }
in keyof 与 keyof 综合应用
模板字符类型
// 模板字符串类型只可以使用 string | number | bigint | boolean | null | undefined
// 变通的方法是将泛型 T 与字符串组成交叉类型,这样就可以使用模板字符串类型
type StrTemplate<T> = `${T & string} is a string`;
type TestStr = StrTemplate<"aaa">; // 'aaa is a string'
综合应用
type MethodsType = {
menu: {
title: string;
icon: string;
};
tabs: {
key: string;
content: string;
};
};
type Template<T, U> = `${T & string}/${U & string}`;
type TestTemplate = Template<"menu", "title" | "icon">;
type SpliceKeys<T extends object> = {
[K in keyof T]: Template<K, keyof T[K]>;
}[keyof T]; // 最后面的[keyof T]可以让[K in keyof T]舍去,只保留 Template<K, keyof T[K]> 的结果
type A = SpliceKeys<MethodsType>; // 'menu/title' | 'menu/icon' | 'tabs/key' | 'tabs/content'
这里定义了一些类型来操作和组合字符串类型。
首先,Template<T, U>
是一个类型模板,它接受两个泛型参数 T
和 U
,并将它们组合成一个字符串类型,格式为 ${T & string}/${U & string}
。这里的 T & string
和 U & string
确保 T
和 U
都是字符串类型。这种类型模板在处理字符串拼接时非常有用。
接下来,TestTemplate
是 Template
类型的一个具体实例,它将 "menu"
和 "title" | "icon"
作为参数。结果是一个联合类型 "menu/title" | "menu/icon"
,表示可能的字符串组合。
当 U
是一个联合类型("title" | "icon"
)时,TypeScript 会将模板字面量类型应用到联合类型的每一个成员上。这意味着 Template<"menu", "title" | "icon">
会被展开为:
${"menu" & string}/${"title" & string}
| ${"menu" & string}/${"icon" & string}
这实际上等价于:
"menu/title" | "menu/icon"
然后,我们定义了 SpliceKeys<T extends object>
类型,它接受一个对象类型 T
作为参数。这个类型使用了映射类型 [K in keyof T]
来遍历 T
的所有键 K
,并将每个键 K
与其对应值的键 keyof T[K]
组合成 Template<K, keyof T[K]>
类型。最后,通过 [keyof T]
,将所有这些组合的结果联合起来,形成一个新的联合类型。
最后,A
是 SpliceKeys<MethodsType>
的具体实例。MethodsType
是一个对象类型,包含两个键 menu
和 tabs
,每个键对应一个对象。通过 SpliceKeys
类型,我们将 MethodsType
的键和值的键组合起来,得到 'menu/title' | 'menu/icon' | 'tabs/key' | 'tabs/content'
这样的联合类型。
这段代码展示了如何使用 TypeScript 的高级类型特性来动态生成字符串类型,极大地增强了类型系统的表达能力。