chengaofeng
发布于 2024-08-01 / 9 阅读
0
0

Typescript条件类型

// 直接使用联合类型判断,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'
  1. 联合类型整体判断

    type Test = string | number | boolean extends string ? string : never; // never

    这里 string | number | boolean extends string 被视为整体进行判断。因为联合类型 string | number | boolean 并不完全是 string 类型,所以结果是 never

  2. 单个类型判断

    type Test2 = string extends string | number | boolean ? "yes" : "no"; // 'yes'

    这里 string extends string | number | boolean 是单个类型判断。因为 stringstring | number | boolean 的子类型,所以结果是 "yes"

type CondType<T> = T extends string ? T : never;

// 可以发现,使用泛型的类型是一个一个进行比较的,只要有一个满足条件,就会返回这个类型

type Test3 = CondType<string | number | boolean>; // string
  1. 泛型条件类型

    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
  1. 封装通用工具

    type CommonType<T> = T extends object ? T : never;

    这里 CommonType 是一个通用的条件类型工具,用于约束类型为对象类型。如果 T 是对象类型,则返回 T,否则返回 never

    • CommonType<Person> -> Person

    • CommonType<string> -> never

    • CommonType<number> -> never

条件类型的应用

条件类型在 TypeScript 中非常有用,特别是在以下场景中:

  1. 类型推断:根据条件推断出不同的类型。

  2. 类型转换:将一种类型转换为另一种类型。

  3. 类型保护:确保代码在运行时的类型安全。

  4. 类型映射:操作对象和数组类型。

示例

以下是一个简单的条件类型示例,用于提取函数的参数类型:

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> 是一个类型模板,它接受两个泛型参数 TU,并将它们组合成一个字符串类型,格式为 ${T & string}/${U & string}。这里的 T & stringU & string 确保 TU 都是字符串类型。这种类型模板在处理字符串拼接时非常有用。

接下来,TestTemplateTemplate 类型的一个具体实例,它将 "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],将所有这些组合的结果联合起来,形成一个新的联合类型。

最后,ASpliceKeys<MethodsType> 的具体实例。MethodsType 是一个对象类型,包含两个键 menutabs,每个键对应一个对象。通过 SpliceKeys 类型,我们将 MethodsType 的键和值的键组合起来,得到 'menu/title' | 'menu/icon' | 'tabs/key' | 'tabs/content' 这样的联合类型。

这段代码展示了如何使用 TypeScript 的高级类型特性来动态生成字符串类型,极大地增强了类型系统的表达能力。


评论