如何在 TypeScript 中使用泛型

更新日期: 2023-01-11阅读: 753标签: 泛型

介绍

泛型是静态类型语言的一个基本特征,允许开发人员将类型作为参数传递给另一个类型、函数或其他结构。当开发人员使他们的组件成为通用组件时,他们赋予该组件接受和强制执行在使用该组件时传入的类型的能力,这提高了代码灵活性,使组件可重用,并消除了重复。

TypeScript完全支持泛型作为一种将类型安全引入组件的方式,这些组件接受参数和返回值,这些参数和返回值的类型在稍后在代码中使用之前是不确定的。在本教程中,您将尝试 TypeScript 泛型的真实示例,并探索它们如何在函数、类型、类和接口中使用。您还将使用泛型来创建映射类型和条件类型,这将帮助您创建可灵活应用于代码中所有必要情况的 TypeScript 组件。

泛型语法

在开始泛型的应用之前,本教程将首先介绍 TypeScript 泛型的语法,然后通过一个示例来说明它们的一般用途。

泛型出现在尖括号内的 TypeScript 代码中,格式为,其中代表传入的类型。可以理解为 type 的泛型。在这种情况下,将以与参数在函数中的工作方式相同的方式运行,作为将在创建结构实例时声明的类型的占位符。因此,在尖括号内指定的泛型类型也称为泛型类型参数或简称为类型参数。多个泛型类型也可以出现在单个定义中,例如.<T>T<T>TT<T, K, A>

注意:按照惯例,程序员通常使用单个字母来命名泛型类型。这不是语法规则,您可以像 TypeScript 中的任何其他类型一样命名泛型,但这种约定有助于立即向阅读您的代码的人传达通用类型不需要特定类型的信息。

泛型可以出现在函数、类型、类和接口中。这些结构中的每一个都将在本教程的后面部分介绍,但现在将使用一个函数作为示例来说明泛型的基本语法。

要了解泛型有多大用处,请假设您有一个JavaScript 函数,它接受两个参数:一个对象和一个键数组。该函数将返回一个基于原始对象的新对象,但只包含您想要的键:

function pickObjectKeys(obj, keys) {
  let result = {}
  for (const key of keys) {
    if (key in obj) {
      result[key] = obj[key]
    }
  }
  return result
}

此代码段显示了pickObjectKeys()函数,它遍历keys数组并使用数组中指定的键创建一个新对象。

这是一个显示如何使用该功能的示例:

const language = {
  name: "TypeScript",
  age: 8,
  extensions: ['ts', 'tsx']
}

const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])

这声明了一个对象,然后将和属性与函数language隔离开来。的值将如下所示:ageextensionspickObjectKeys()ageAndExtensions

{
  age: 8,
  extensions: ['ts', 'tsx']
}

如果要将此代码迁移到 TypeScript 以使其类型安全,则必须使用泛型。您可以通过添加以下突出显示的行来重构代码:

function pickObjectKeys<T, K extends keyof T>(obj: T, keys: K[]) {
  let result = {} as Pick<T, K>
  for (const key of keys) {
    if (key in obj) {
      result[key] = obj[key]
    }
  }
  return result
}

const language = {
  name: "TypeScript",
  age: 8,
  extensions: ['ts', 'tsx']
}

const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])

<T, K extends keyof T>为函数声明两个参数类型,其中K分配的类型是T. 然后将obj函数参数设置为代表的任何类型T,以及代表keys任何类型的数组K。由于T在language对象集age为数字和extensions字符串数组的情况下,ageAndExtensions现在将为变量分配具有属性age: number和的对象类型extensions: string[]。

这会根据提供给 的参数强制执行返回类型pickObjectKeys,从而允许函数在知道需要强制执行的特定类型之前灵活地强制执行类型结构。当在 IDE(如 Visual Studio Code)中使用该功能时,这也增加了更好的开发人员体验,它将keys根据您提供的对象为参数创建建议。这显示在以下屏幕截图中:


了解如何在 TypeScript 中创建泛型后,您现在可以继续探索在特定情况下使用泛型。本教程将首先介绍如何在函数中使用泛型。

将泛型与函数一起使用

将泛型与函数一起使用的最常见场景之一是当您有一些代码不容易为所有用例键入时。为了使该功能适用于更多情况,您可以包括泛型类型。在此步骤中,您将运行一个identity函数示例来说明这一点。您还将探索一个异步示例,了解何时将类型参数直接传递给您的泛型,以及如何为您的泛型类型参数创建约束和默认值。

分配通用参数

看一下下面的函数,它返回作为第一个参数传入的内容:

function identity(value) {
  return value;
}

您可以添加以下代码以使函数在 TypeScript 中类型安全:

function identity<T>(value: T): T {
  return value;
}

您将函数转换为接受泛型类型参数的泛型函数T,这是第一个参数的类型,然后将返回类型设置为与 相同: T。

接下来,添加以下代码来试用该功能:

function identity<T>(value: T): T {
  return value;
}

const result = identity(123);

result具有类型123,这是您传入的确切数字。这里的 TypeScript 从调用代码本身推断泛型类型。这样调用代码不需要传递任何类型参数。您也可以显式地将泛型类型参数设置为您想要的类型:

function identity<T>(value: T): T {
  return value;
}

const result = identity<number>(123);

在此代码中,result具有类型number. 通过在<number>代码中传递类型,您明确地让 TypeScript 知道您希望函数的泛型类型参数T是identitytype number。这将强制number类型作为参数和返回值。

直接传递类型参数

直接传递类型参数在使用自定义类型时也很有用。例如,看看下面的代码:

type ProgrammingLanguage = {
  name: string;
};

function identity<T>(value: T): T {
  return value;
}

const result = identity<ProgrammingLanguage>({ name: "TypeScript" });

在此代码中,result具有自定义类型ProgrammingLanguage,因为它直接传递给identity函数。如果您没有明确包含类型参数,result则将使用类型{ name: string }代替。

使用 JavaScript 时的另一个常见示例是使用包装函数从 api 检索数据

async function fetchApi(path: string) {
  const response = await fetch(`https://example.com/api${path}`)
  return response.json();
}

此异步函数将 URL 路径作为参数,使用fetch API向 URL 发出请求,然后返回JSON响应值。在这种情况下,fetchApi函数的返回类型将为Promise<any>,这是对json()获取response对象的调用的返回类型。

any作为返回类型并不是很有帮助。any表示任何 JavaScript 值,使用它你将失去静态类型检查,这是 TypeScript 的主要优点之一。如果您知道 API 将返回给定形状的对象,则可以使用泛型使此函数类型安全:

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

突出显示的代码将您的函数转换为接受ResultType泛型类型参数的泛型函数。此泛型类型用于函数的返回类型:Promise<ResultType>。

注意:由于您的功能是async,因此您必须返回一个Promise对象。TypeScriptPromise类型本身是一种通用类型,它接受 promise 解析为的值的类型。

如果仔细查看您的函数,您会发现参数列表或 TypeScript 能够推断其值的任何其他地方都没有使用泛型。这意味着调用代码在调用您的函数时必须显式传递此泛型的类型。

fetchApi这是检索用户数据的通用函数的可能实现:

type User = {
  name: string;
}

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

const data = await fetchApi<User[]>('/users')

export {}

在此代码中,您将创建一个名为 的新类型User,并使用该类型的数组 ( User[]) 作为ResultType泛型参数的类型。该data变量现在具有类型User[]而不是any.

注意:当您await用于异步处理函数的结果时,返回类型将是Tin的类型Promise<T>,在本例中是泛型类型ResultType。

默认类型参数

像您一样创建通用fetchApi函数,调用代码始终必须提供类型参数。如果调用代码不包含泛型类型,ResultType则将绑定到unknown. 以下面的实现为例:

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return 
response.json();
}

const data = await fetchApi('/users')

console.log(data.a)

export {}

此代码尝试访问 的理论a属性data。但由于 的类型data是unknown,此代码将无法访问对象的属性。

如果您不打算将特定类型添加到泛型函数的每次调用中,则可以将默认类型添加到泛型类型参数中。这可以通过= DefaultType在泛型类型之后添加来完成,如下所示:

async function fetchApi<ResultType = Record<string, any>>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

const data = await fetchApi('/users')

console.log(data.a)

export {}

使用此代码,您不再需要ResultType在调用函数时将类型传递给泛型参数fetchApi,因为它具有默认类型Record<string, any>. 这意味着 TypeScript 将识别data为具有 type 键和 typestring值的对象any,从而允许您访问其属性。

类型参数约束

在某些情况下,泛型类型参数需要只允许将某些形状传递给泛型。要为您的泛型创建额外的特殊层,您可以对您的参数施加约束。

假设您有一个存储限制,您只能存储所有属性都具有字符串值的对象。为此,您可以创建一个函数,它接受任何对象并返回另一个对象,该对象具有与原始对象相同的键,但所有值都转换为字符串。这个函数将被调用stringifyObjectKeyValues。

这个函数将是一个通用函数。这样,您就可以使生成的对象具有与原始对象相同的形状。该函数将如下所示:

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  return Object.keys(obj).reduce((acc, key) =>  ({
    ...acc,
    [key]: JSON.stringify(obj[key])
  }), {} as { [K in keyof T]: string })
}

在此代码中,stringifyObjectKeyValues使用reduce数组方法迭代原始键数组,将值字符串化并将它们添加到新数组中。

为确保调用代码始终将对象传递给您的函数,您在泛型类型上使用类型约束T,如以下突出显示的代码所示:

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  // ...
}

extends Record<string, any>被称为通用类型约束,它允许您指定您的通用类型必须可分配给extends关键字后面的类型。在这种情况下,Record<string, any>指示具有 type 的键和 type 的string值的对象any。您可以让您的类型参数扩展任何有效的 TypeScript 类型。

调用reduce时,reducer 函数的返回类型基于累加器的初始值。该代码通过对空对象进行类型{} as { [K in keyof T]: string }转换,将累加器初始值的类型设置为。该类型使用与 相同的键创建一个新类型,但所有值都设置为具有该类型。这称为映射类型,本教程将在后面的部分中进一步探讨。{ [K in keyof T]: string }{}{ [K in keyof T]: string }Tstring

以下代码显示了您的stringifyObjectKeyValues函数的实现:

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  return Object.keys(obj).reduce((acc, key) =>  ({
    ...acc,
    [key]: JSON.stringify(obj[key])
  }), {} as { [K in keyof T]: string })
}

const stringifiedValues = stringifyObjectKeyValues({ a: "1", b: 2, c: true, d: [1, 2, 3]})

该变量stringifiedValues将具有以下类型:

{
  a: string;
  b: string;
  c: string;
  d: string;
}

这将确保返回值与函数的目的一致。

本节介绍了将泛型与函数一起使用的多种方法,包括直接分配类型参数以及为参数形状设置默认值和约束。接下来,您将通过一些示例了解泛型如何使接口和类适用于更多情况。

将泛型与接口、类和类型一起使用

在 TypeScript 中创建接口和类时,使用泛型类型参数来设置结果对象的形状会很有用。例如,一个类可能具有不同类型的属性,具体取决于传递给构造函数的内容。在本节中,您将了解在类和接口中声明泛型类型参数的语法,并检查 HTTP 应用程序中的常见用例。

通用接口和类

要创建通用接口,您可以在接口名称之后添加类型参数列表:

interface MyInterface<T> {
  field: T
}

这声明了一个接口,该接口具有一个属性field,其类型由传入的类型确定T。

对于类,语法几乎相同:

class MyClass<T> {
  field: T
  constructor(field: T) {
    this.field = field
  }
}

通用接口/类的一个常见用例是当您有一个字段,其类型取决于客户端代码如何使用接口/类时。假设您有一个HttpApplication用于处理对 API 的 HTTP 请求的类,并且某些上下文值将传递给每个请求处理程序。这样做的一种方法是:

class HttpApplication<Context> {
  context: Context
	constructor(context: Context) {
    this.context = context;
  }

  // ... implementation

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

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

...
const context = { someValue: true };
const app = new HttpApplication(context);

app.get('/api', async () => {
  console.log(context.someValue)
});

在此实现中,TypeScript 将推断context.someValueas的类型boolean。

通用类型

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

type MyIdentityType<T> = T

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

...
type B = MyIdentityType<number>

在这种情况下,类型B将是 type number。

通用类型通常用于创建辅助类型,尤其是在使用映射类型时。TypeScript 提供了许多预构建的帮助程序类型。一个这样的例子是Partial类型,它接受一个类型T并返回另一个与 形状相同的类型T,但它们的所有字段都设置为可选。的实现Partial看起来像这样:

type Partial<T> = {
  [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。从一家商店到它自己的运费是null,因为根本没有运费。

为了确保其他商店的位置具有一致的价值,并且商店运送到自己的总是null,您可以创建一个通用的帮助器类型:

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

该类型IfSameKeyThanParentTOtherwiseOtherType接收三个通用类型。第一个Keys是您要确保对象具有的所有键。在这种情况下,它是所有商店代码的联合。T是当嵌套对象字段具有与父对象上的键相同的键时的类型,在这种情况下,它表示运送到自身的商店位置。最后,OtherType是当键不同时的类型,代表一家商店运送到另一家商店。

你可以像这样使用它:

...
type Code = 'ABC' | 'DEF' | 'GHI'

const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType<Code, null, number> = {
  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<Code, null, number> = {
  ABC: {
    ABC: 12,
    DEF: 12,
    GHI: 13,
  },
  DEF: {
    ABC: 12,
    DEF: null,
    GHI: 17,
  },
  GHI: {
    ABC: 13,
    DEF: 17,
    GHI: null,
  },
}

由于 和 本身之间的运费ABC不再是null,TypeScript 将抛出以下错误:

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

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

使用泛型创建映射类型

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

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

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

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

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

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

这种类型的一个使用场景BooleanFields是创建一个选项对象。假设您有一个数据库模型,例如User. 从数据库中获取此模型的记录时,您还将允许传递一个指定要返回哪些字段的对象。该对象将具有与模型相同的属性,但类型设置为布尔值。传入true一个字段意味着您希望它被返回并且false您希望它被省略。

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

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

type User = {
  email: string;
  name: string;
}

type UserFetchOptions = BooleanFields<User>;

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

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

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

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

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

readonly请注意作为前缀添加[K in keyof T]到此代码中的部分的修饰符。目前,可以在映射类型中使用的两个可用修饰符是readonly修饰符,它必须作为前缀添加到属性,以及?修饰符,它可以作为后缀添加到属性。修饰符将?字段标记为可选。两个修饰符都可以接收一个特殊的前缀来指定是否应该删除修饰符 ( -) 或添加 ( +)。如果仅提供修饰符,+则假定为。

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

使用泛型创建条件类型

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

条件类型的基本结构

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

type IsStringType<T> = T extends string ? true : false;

在此代码中,您将创建一个名为 的新泛型类型IsStringType,它接收一个类型参数T. 在您的类型定义中,您使用的语法看起来像使用 JavaScript 中的三元运算符的条件表达式:T extends string ? true : false。此条件表达式正在检查类型是否T扩展了类型string。如果是,则结果类型将是确切的类型true;否则,它将被设置为 type false。

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

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

type IsStringType<T> = T extends string ? true : false;

type A = "abc";
type B = {
  name: string;
};

type ResultA = IsStringType<A>;
type ResultB = IsStringType<B>;

在此代码中,您创建了两种类型,A和B. typeA是字符串文字"abc"的类型,而 typeB是具有称为 type 的属性的对象name的类型string。然后,您将这两种类型与您的IsStringType条件类型一起使用,并将结果类型存储到两个新类型中,ResultA并且ResultB.

如果您检查 and 的结果类型ResultA,ResultB您会注意到该ResultA类型被设置为确切的类型true并且该ResultB类型被设置为false。这是正确的,A扩展string类型和B不扩展类型都是正确的string,因为它被设置为具有单个name属性 type 的对象的类型string。

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

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

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

在此代码中,您将创建一个新的泛型类型,它是一个名为 的条件类型GetReturnType。此泛型类型接受单个类型参数T. 在类型声明本身内部,您正在检查该类型是否T扩展了一个与函数签名匹配的类型,该函数签名接受可变数量的参数(包括零),然后您推断该函数的返回类型创建一个新的类型U,它是可用的在条件的true分支内使用。的类型U将绑定到传递函数的返回值的类型。如果传递的类型T不是函数,则代码将返回类型never。

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

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

function someFunction() {
  return true;
}

type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>;

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

由于someFunction变量的类型是函数,因此条件类型将评估条件的true分支。这将返回类型U作为结果。类型U是从函数的返回类型推断出来的,在本例中是一个boolean. 如果检查 的类型ReturnTypeOfSomeFunction,您会发现它已正确设置为具有该boolean类型。

高级条件类型用例

条件类型是 TypeScript 中可用的最灵活的功能之一,允许创建一些高级实用程序类型。在本节中,您将通过创建一个名为Nestedomit<T, KeysToOmit>. 此实用程序类型将能够省略对象中的字段,就像现有的Omit<T, KeysToOmit>实用程序类型一样,但也允许使用点表示法省略嵌套字段。

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

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

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<string, any>类型。这将是您要从中省略属性的对象的类型。第二个类型参数称为KeysToOmit,它必须是类型string。您将使用它来指定要从您的类型中省略的键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"。

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

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<T, KeyPart1>
        & {
          [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
        }

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

您正在使用Omit删除 中的一些嵌套字段,T[KeyPart1]为此,您必须重建 的类型T[KeyPart1]。为避免重建整个T类型,您使用仅从Omit中删除,保留其他字段。然后你将在下一部分重建类型。KeyPart1TT[KeyPart1]

[NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>是一个映射类型,其中属性是可分配给的属性KeyPart1,这意味着您刚刚从中提取的部分KeysToOmit。这是您要删除的字段的父项。如果您通过了a.b.c,那么在对您的状况进行第一次评估时将会是NewKeys in "a"。然后将此属性的类型设置为递归调用实用程序类型的结果,但现在通过使用NestedOmit将此属性的类型作为第一个类型参数传递给内部,并作为第二个类型参数传递点表示法中的其余键, 可在.TT[NewKeys]KeyPart2

在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<T, KeyPart1>
        & {
          [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2>
        }
      : T

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

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

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

如果条件KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`为false,则表示KeysToOmit未使用点符号,因此您可以使用现有的Omit实用程序类型。

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

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

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

type Result = NestedOmit<NestedObject, "a.b.c">;

在第一次评估条件类型时,外部条件将是true,因为字符串文字类型"a.b.c"可分配给模板文字类型`${infer KeyPart1}.${infer KeyPart2}`。在这种情况下,KeyPart1将被推断为字符串文字类型"a",KeyPart2并将被推断为字符串的剩余部分,在本例中为"b.c".

现在将评估内部条件。这将评估为true,因为KeyPart1此时是 的关键T。KeyPart1现在是"a",并且T确实有一个属性"a":

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

继续评估条件,您现在位于内部true分支中。这将构建一个新类型,它是其他两种类型的交集。第一种类型是使用Omit实用程序类型 onT来省略可分配给 的字段的结果KeyPart1,在本例中为a字段。第二种类型是您通过NestedOmit递归调用构建的新类型。

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

在 的最终评估中NestedOmit,第一个条件将返回false,因为传递的字符串类型是 just "c"now。发生这种情况时,您可以使用内置助手从对象中省略该字段。这将返回b字段的类型,这是c省略的原始类型。现在评估结束,TypeScript 返回您要使用的新类型,并省略嵌套字段。

结论

在本教程中,您将探索适用于函数、接口、类和自定义类型的泛型。您还使用了泛型来创建映射类型和条件类型。这些都使泛型成为您在使用 TypeScript 时可以随意使用的强大工具。正确使用它们将使您免于一遍又一遍地重复代码,并使您编写的类型更加灵活。如果您是图书馆作者并且计划让您的代码对广大读者来说更易读,则尤其如此。

翻译来自:https://www.digitalocean.com/community/tutorials/how-to-use-generics-in-typescript

链接: https://fly63.com/article/detial/12320

TypeScript 泛型的通俗解释

在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束。这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型。那么,究竟什么是泛型?如果通俗的理解泛型呢?

泛型,很多人因它放弃学习 TypeScript?

Java是和typescript一样支持泛型的,当我在大学开始学习Java的时候,我还是一个菜鸟码农,遇到难点(比如泛型)就直接跳过,能学多少学多少,回寝室就LOL开黑。直到大学毕业我依旧没有理解泛型的概念

TypeScript泛型参数默认类型 和 新的 --strict 编译选项

TypeScript 2.3 增加了对声明泛型参数默认类型的支持,允许为泛型类型中的类型参数指定默认类型。接下来看看如何通过泛型参数默认将以下React组件从 JS (和JSX)迁移到 TypeScript (和TSX):

这些高阶ts内置泛型帮助类型,你用过几个

本文将简要介绍一些工具泛型使用及其实现, 这些泛型接口定义大多数是语法糖(简写), 你可以在 typescript 包中的 lib.es5.d.ts 中找到它的定义, 我们项目的版本

TypeScript的索引类型与映射类型,以及常用工具泛型的实现

相信现在很多小伙伴都在使用 TypeScript(以下简称 TS),在 TS 中除了一些常用的基本类型外,还有一些稍微高级一点的类型,这些就是我本次文章要讲的内容

一文读懂 TypeScript 泛型及应用

泛型是静态类型语言的基本特征,允许将类型作为参数传递给另一个类型、函数、或者其他结构。TypeScript 支持泛型作为将类型安全引入组件的一种方式。

秒懂 TypeScript 泛型工具类型!

如果你刚接触 TypeScript 不久,在阅读 TypeScript 内置工具类型的用法和内部实现的文章时,可能会看到 Pick 工具类型,对于该类型的语法你可能会感到陌生。

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!