class HttpApplication {  context: Context  constructor(context: Context) {    this.context = context;  }
// ... implementation
get(url: string, handler: (context: Context) => Promise<void>): this { // ... implementation return this; }}

此类存储一个上下文,其类型作为 get 方法中处理函数的参数类型传入。 在使用过程中,传递给 get 处理程序的参数类型将从传递给类构造函数的内容中正确推断出来。

...const context = { someValue: true };const app = new HttpApplication(context);
app.get('/api', async () => { console.log(context.someValue)});

在此实现中,TypeScript 会将 context.someValue 的类型推断为布尔值。

通用类型

现在已经了解了类和接口中泛型的一些示例,您现在可以继续创建泛型自定义类型。 将泛型应用于类型的语法类似于将泛型应用于接口和类的语法。 看看下面的代码:

type MyIdentityType = T

此泛型类型返回作为类型参数传递的类型。 假设您使用以下代码实现了这种类型:

...type B = MyIdentityType<number>

在这种情况下泛型方法,类型 B 将是类型 number。

通用类型通常用于创建辅助类型,尤其是在使用映射类型时。 TypeScript 提供了许多预构建的帮助程序类型。

一个这样的例子是 Partial 类型,它采用类型 T 并返回另一个与 T 具有相同形状的类型,但它们的所有字段都设置为可选。 Partial 的实现如下所示:

type Partial = {  [P in keyof T]?: T[P];};

这里的 Partial 类型接受一个类型,遍历其属性类型,然后将它们作为可选类型返回到新类型中。

注意:由于 Partial 已经内置到 TypeScript 中,因此将此代码编译到您的 TypeScript 环境中会重新声明 Partial 并引发错误。 这里引用的Partial的实现只是为了说明。

要了解泛型类型有多么强大,假设您有一个对象字面量,用于存储从一家商店到您的业务分销网络中所有其他商店的运输成本。 每个商店将由一个三字符代码标识,如下所示:

{  ABC: {    ABC: null,    DEF: 12,    GHI: 13,  },  DEF: {    ABC: 12,    DEF: null,    GHI: 17,  },  GHI: {    ABC: 13,    DEF: 17,    GHI: null,  },}

该对象是表示商店位置的对象的集合。 在每个商店位置中,都有表示运送到其他商店的成本的属性。 例如,从 ABC 运往 DEF 的成本是 12。从一家商店到它自己的运费为空,因为根本没有运费。

为确保其他商店的位置具有一致的值,并且商店运送到自身的始终为空,您可以创建一个通用的帮助器类型:

type IfSameKeyThanParentTOtherwiseOtherType<Keys extends string, T, OtherType> = {  [K in Keys]: {    [SameThanK in K]: T;  } &    { [OtherThanK in Exclude]: OtherType };};

IfSameKeyThanParentTOtherwiseOtherType 类型接收三个通用类型。 第一个,Keys,是你想要确保你的对象拥有的所有键。 在这种情况下泛型方法,它是所有商店代码的联合。

T 是当嵌套对象字段具有与父对象上的键相同的键时的类型,在这种情况下,它表示运送到自身的商店位置。 最后,OtherType 是 key 不同时的类型,表示一个商店发货到另一个商店。

你可以像这样使用它:

...type Code = 'ABC' | 'DEF' | 'GHI'
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType = { ABC: { ABC: null, DEF: 12, GHI: 13, }, DEF: { ABC: 12, DEF: null, GHI: 17, }, GHI: { ABC: 13, DEF: 17, GHI: null, },}

此代码现在强制执行类型形状。 如果您将任何键设置为无效值,TypeScript 将报错:

...const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType = {  ABC: {    ABC: 12,    DEF: 12,    GHI: 13,  },  DEF: {    ABC: 12,    DEF: null,    GHI: 17,  },  GHI: {    ABC: 13,    DEF: 17,    GHI: null,  },}

由于 ABC 与自身之间的运费不再为空,TypeScript 将抛出以下错误:

OutputType 'number' is not assignable to type 'null'.(2322)

您现在已经尝试在接口、类和自定义帮助程序类型中使用泛型。 接下来,您将进一步探讨本教程中已经多次出现的主题:使用泛型创建映射类型。

使用泛型创建映射类型

在使用 TypeScript 时,有时您需要创建一个与另一种类型具有相同形状的类型。 这意味着它应该具有相同的属性,但属性的类型设置为不同的东西。 对于这种情况,使用映射类型可以重用初始类型形状并减少应用程序中的重复代码。

在 TypeScript 中,这种结构被称为映射类型并依赖于泛型。 在本节中,您将看到如何创建映射类型。

想象一下,您想要创建一个类型,给定另一个类型,该类型返回一个新类型,其中所有属性都设置为具有布尔值。 您可以使用以下代码创建此类型:

type BooleanFields = {  [K in keyof T]: boolean;}

在这种类型中,您使用语法 [K in keyof T] 来指定新类型将具有的属性。 keyof T 运算符用于返回具有 T 中所有可用属性名称的联合。然后使用 K in 语法指定新类型的属性是返回的联合类型中当前可用的所有属性 T键。

这将创建一个名为 K 的新类型,它绑定到当前属性的名称。 这可用于使用语法 T[K] 访问原始类型中此属性的类型。 在这种情况下,您将属性的类型设置为布尔值。

此 BooleanFields 类型的一个使用场景是创建一个选项对象。 假设您有一个数据库模型,例如用户。

从数据库中获取此模型的记录时,您还将允许传递一个指定要返回哪些字段的对象。

该对象将具有与模型相同的属性,但类型设置为布尔值。 在一个字段中传递 true 意味着您希望它被返回,而 false 则意味着您希望它被省略。

您可以在现有模型类型上使用 BooleanFields 泛型来返回与模型具有相同形状的新类型,但所有字段都设置为布尔类型,如以下突出显示的代码所示:

type BooleanFields = {  [K in keyof T]: boolean;};
type User = { email: string; name: string;}
type UserFetchOptions = BooleanFields;

在此示例中,UserFetchOptions 将与这样创建它相同:

type UserFetchOptions = {  email: boolean;  name: boolean;}

创建映射类型时,您还可以为字段提供修饰符。 一个这样的例子是 TypeScript 中可用的现有泛型类型,称为 Readonly。 Readonly 类型返回一个新类型,其中传递类型的所有属性都设置为只读属性。 这种类型的实现如下所示:

type Readonly = {  readonly [K in keyof T]: T[K]}

注意:由于 Readonly 已经内置到 TypeScript 中,因此将此代码编译到您的 TypeScript 环境中会重新声明 Readonly 并引发错误。 这里引用的Readonly的实现只是为了说明的目的。

请注意修饰符 readonly,它作为前缀添加到此代码中的 [K in keyof T] 部分。

目前,可以在映射类型中使用的两个可用修饰符是 readonly 修饰符,它必须作为前缀添加到属性,以及 ? 修饰符,可以作为属性的后缀添加。 这 ? 修饰符将字段标记为可选。

两个修饰符都可以接收一个特殊的前缀来指定是否应该删除修饰符 (-) 或添加 (+)。 如果仅提供修饰符,则假定为 +。

现在您可以使用映射类型基于您已经创建的类型形状创建新类型,您可以继续讨论泛型的最终用例:条件类型。

使用泛型创建条件类型

在本节中,您将尝试 TypeScript 中泛型的另一个有用功能:创建条件类型。 首先,您将了解条件类型的基本结构。 然后,您将通过创建一个条件类型来探索高级用例,该条件类型省略基于点表示法的对象类型的嵌套字段。

条件类型的基本结构

条件类型是根据某些条件具有不同结果类型的泛型类型。 例如,看看下面的泛型类型 IsStringType:

type IsStringType = T extends string ? true : false;

在此代码中,您正在创建一个名为 IsStringType 的新泛型类型,它接收单个类型参数 T。在您的类型定义中,您使用的语法看起来像使用 JavaScript 中的三元运算符的条件表达式:T extends string ? 真假。

此条件表达式正在检查类型 T 是否扩展了类型字符串。 如果是,则结果类型将是完全正确的类型; 否则,它将被设置为 false 类型。

注意:此条件表达式是在编译期间求值的。 TypeScript 仅适用于类型,因此请确保始终将类型声明中的标识符读取为类型,而不是值。 在此代码中,您使用每个布尔值的确切类型,true 和 false。

要尝试这种条件类型,请将一些类型作为其类型参数传递:

type IsStringType = T extends string ? true : false;
type A = "abc";type B = { name: string;};
type ResultA = IsStringType;type ResultB = IsStringType;

在此代码中,您创建了两种类型,A 和 B。类型 A 是字符串文字“abc”的类型,而类型 B 是具有名为 name of type string 属性的对象的类型。

然后将这两种类型与 IsStringType 条件类型一起使用,并将结果类型存储到两个新类型 ResultA 和 ResultB 中。

如果检查 ResultA 和 ResultB 的结果类型,您会注意到 ResultA 类型设置为准确的类型 true,而 ResultB 类型设置为 false。 这是正确的,因为 A 确实扩展了字符串类型而 B 没有扩展字符串类型,因为它被设置为具有字符串类型的单个名称属性的对象的类型。

条件类型的一个有用特性是它允许您使用特殊关键字 infer 在 extends 子句中推断类型信息。 然后可以在条件的真实分支中使用这种新类型。 此功能的一种可能用法是检索任何函数类型的返回类型。

编写以下 GetReturnType 类型来说明这一点:

type GetReturnType = T extends (...args: any[]) => infer U ? U : never;

在此代码中,您将创建一个新的泛型类型,它是一个名为 GetReturnType 的条件类型。 此泛型类型接受单个类型参数 T。

在类型声明本身内部,您正在检查类型 T 是否扩展了与函数签名匹配的类型,该函数签名接受可变数量的参数(包括零),然后您推断返回 该函数的类型创建一个新类型 U,可在条件的真实分支内使用。

U 的类型将绑定到传递函数的返回值的类型。 如果传递的类型 T 不是函数,则代码将返回 never 类型。

使用您的类型和以下代码:

type GetReturnType = T extends (...args: any[]) => infer U ? U : never;
function someFunction() { return true;}
type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>;

在此代码中,您将创建一个名为 someFunction 的函数,该函数返回 true。 然后使用 typeof 运算符将此函数的类型传递给 GetReturnType 泛型,并将结果类型存储在 ReturnTypeOfSomeFunction 类型中。

由于 someFunction 变量的类型是函数,因此条件类型将评估条件的真实分支。 这将返回类型 U 作为结果。

类型 U 是从函数的返回类型推断出来的,在本例中是布尔值。 如果检查 ReturnTypeOfSomeFunction 的类型,您会发现它已正确设置为布尔类型。

高级条件类型用例

条件类型是 TypeScript 中可用的最灵活的功能之一,允许创建一些高级实用程序类型。

在本节中,您将通过创建一个名为 NestedOmit 的条件类型来探索这些用例之一。

此实用程序类型将能够省略对象中的字段,就像现有的 Omit 实用程序类型一样,但也允许使用点表示法省略嵌套字段。

使用新的 NestedOmit 泛型,您将能够使用以下示例中所示的类型:

type SomeType = {  a: {    b: string,    c: {      d: number;      e: string[]    },    f: number  }  g: number | string,  h: {    i: string,    j: number,  },  k: {    l: number,  }}
type Result = NestedOmit<SomeType, "a.b" | "a.c.e" | "h.i" | "k">;

此代码声明了一个名为 SomeType 的类型,它具有嵌套属性的多级结构。 使用 NestedOmit 泛型,传入类型,然后列出要省略的属性的键。

请注意如何在第二个类型参数中使用点符号来标识要省略的键。 然后将结果类型存储在 Result 中。

构造此条件类型将使用 TypeScript 中可用的许多功能,例如,模板文字类型、泛型、条件类型和映射类型。

要尝试这个泛型,首先创建一个名为 NestedOmit 的泛型类型,它接受两个类型参数:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string>

第一个类型参数称为 T,它必须是可分配给 Record 类型的类型。 这将是您要从中省略属性的对象的类型。

第二个类型参数叫做KeysToOmit,必须是字符串类型。 您将使用它来指定要从类型 T 中省略的键。

接下来,通过添加以下突出显示的代码来检查 KeysToOmit 是否可分配给 ${infer KeyPart1}.${infer KeyPart2} 类型:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string>=  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`

在这里,您使用模板文字字符串类型,同时,利用条件类型推断模板文字本身内部的其他两种类型。

通过推断模板文字字符串类型的两个部分,您将字符串拆分为另外两个字符串。 第一部分将分配给 KeyPart1 类型,并将包含第一个点之前的所有内容。

第二部分将分配给 KeyPart2 类型,并将包含第一个点之后的所有内容。 如果您将“a.b.c”作为 KeysToOmit 传递,则最初 KeyPart1 将设置为确切的字符串类型“a”,而 KeyPart2 将设置为“b.c”。

接下来,您将添加三元运算符来定义条件的第一个真分支:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`    ?      KeyPart1 extends keyof T

这使用 KeyPart1 extends keyof T 来检查 KeyPart1 是否是给定类型 T 的有效属性。如果您确实有一个有效的键,请添加以下代码以使条件计算为两种类型之间的交集:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`    ?      KeyPart1 extends keyof T      ?        Omit        & {          [NewKeys in KeyPart1]: NestedOmit        }

Omit 是一种使用 TypeScript 默认附带的 Omit 助手构建的类型。 此时,KeyPart1 不是点表示法:它将包含一个字段的确切名称,该字段包含您希望从原始类型中省略的嵌套字段。 因此,您可以安全地使用现有的实用程序类型。

您正在使用 Omit 删除 T[KeyPart1] 中的一些嵌套字段,为此,您必须重建 T[KeyPart1] 的类型。

为避免重建整个 T 类型,您使用 Omit 仅从 T 中删除 KeyPart1,同时保留其他字段。 然后,您将在下一部分的类型中重建 T[KeyPart1]。

[KeyPart1 中的新键]:NestedOmit 是一个映射类型,其中属性是可分配给 KeyPart1 的属性,这意味着您刚刚从 KeysToOmit 中提取的部分。

这是您要删除的字段的父项。 如果您通过了 a.b.c,在第一次评估您的条件时,它将是“a”中的 NewKeys。

然后将此属性的类型设置为递归调用 NestedOmit 实用程序类型的结果,但现在使用 T[NewKeys] 将此属性的类型作为第一个类型参数传递给 T,并作为第二个类型参数传递其余键以点表示法表示,在 KeyPart2 中可用。

泛型方法_c 泛型方法_java 静态方法 泛型

在内部条件的 false 分支中,返回绑定到 T 的当前类型,就好像 KeyPart1 不是 T 的有效键一样:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`    ?      KeyPart1 extends keyof T      ?        Omit        & {          [NewKeys in KeyPart1]: NestedOmit        }      : T

条件的这个分支意味着你试图省略一个 T 中不存在的字段。在这种情况下,没有必要再进一步了。

最后,在外部条件的 false 分支中,使用现有的 Omit 实用程序类型从 Type 中省略 KeysToOmit:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`    ?      KeyPart1 extends keyof T      ?        Omit        & {          [NewKeys in KeyPart1]: NestedOmit        }      : T    : Omit;

如果条件 KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` 为假,这意味着 KeysToOmit 没有使用点符号,因此,您可以使用现有的 Omit 实用程序类型。

现在,要使用新的 NestedOmit 条件类型,请创建一个名为 NestedObject 的新类型:

type NestedObject = {  a: {    b: {      c: number;      d: number;    };    e: number;  };  f: number;};

然后对其调用 NestedOmit 以省略 a.b.c 处可用的嵌套字段:

type Result = NestedOmit;

在第一次评估条件类型时,外部条件将为真,因为字符串文字类型“a.b.c”可分配给模板文字类型“${infer KeyPart1}.${infer KeyPart2}”。

在这种情况下,KeyPart1 将被推断为字符串文字类型“a”,而 KeyPart2 将被推断为字符串的剩余部分,在本例中为“b.c”。

现在将评估内部条件。 这将评估为真,因为此时 KeyPart1 是 T 的键。KeyPart1 现在是“a”,而 T 确实有一个属性“a”:

type NestedObject = {a: {b: {c: number;d: number;};e: number;};  f: number;};

继续评估条件,您现在位于内部 true 分支内。 这将构建一个新类型,它是其他两种类型的交集。

第一种类型是在 T 上使用 Omit 实用程序类型以省略可分配给 KeyPart1 的字段的结果,在本例中为 a 字段。 第二种类型是您通过递归调用 NestedOmit 构建的新类型。

如果您进行 NestedOmit 的下一次评估,对于第一次递归调用,交集类型现在正在构建一个类型以用作 a 字段的类型。 这将重新创建一个没有您需要省略的嵌套字段的字段。

在 NestedOmit 的最终评估中,第一个条件将返回 false,因为传递的字符串类型现在只是“c”。 发生这种情况时,您可以使用内置助手从对象中省略该字段。

这将返回 b 字段的类型,即省略了 c 的原始类型。 现在评估结束,TypeScript 返回您要使用的新类型,并省略嵌套字段。

结论

在本教程中,我们探索适用于函数、接口、类和自定义类型的泛型,以及使用了泛型来创建映射类型和条件类型。

这些都使泛型成为您在使用 TypeScript 时可以随意使用的强大工具。 正确使用它们将使您免于一遍又一遍地重复代码,并使您编写的类型更加灵活。

限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688