如何在 TypeScript 中使用装饰器

如何在 TypeScript 中使用装饰器,第1张

TypeScript 是 JavaScript 语言的扩展,它使用 JavaScript 的运行时和编译时类型检查器。

这种组合允许开发人员使用完整的 JavaScript 生态系统和语言功能,同时,还可以在其之上添加可选的静态类型检查、枚举、类和接口。这些额外功能之一是装饰器的支持。

装饰器是一种装饰类成员或类本身的方法,具有额外的功能。

当我们将装饰器应用于类或类成员时,我们实际上是在调用一个函数,该函数将接收被装饰内容的详细信息,然后,装饰器实现将能够动态转换代码,添加额外的功能,并且 减少样板代码。

它们是在 TypeScript 中进行元编程的一种方式,TypeScript 是一种编程技术,使程序员能够创建使用来自应用程序本身的其他代码作为数据的代码。

本教程将分享如何在 TypeScript 中为类和类成员创建自己的装饰器,以及如何使用它们。

它将引导我们完成不同的代码示例,我们可以在自己的 TypeScript 环境或 TypeScript Playground(一个允许我们直接在浏览器中编写 TypeScript 的在线环境)中遵循这些示例。

准备工作

要完成本教程实例,我们需要做如下准备:

在 TypeScript 中启用装饰器支持

目前,装饰器在 TypeScript 中仍然是一个实验性功能,因此,必须先启用它。在本节中,我们将了解如何在 TypeScript 中启用装饰器,具体取决于您使用 TypeScript 的方式。

TypeScript 编译器 CLI

要在使用 TypeScript Compiler CLI (tsc) 时启用装饰器支持,唯一需要的额外步骤是传递一个附加标志 --experimentalDecorators:

tsconfig.json

在具有 tsconfig.json 文件的项目中工作时,要启用实验性装饰器,我们必须将实验性装饰器属性添加到 compilerOptions 对象:

在 TypeScript Playground 中,装饰器默认启用。

使用装饰器语法

在本节中,我们将在 TypeScript 类中应用装饰器。

在 TypeScript 中,我们可以使用特殊语法 @expression 创建装饰器,其中 expression 是一个函数,将在运行时自动调用,其中包含有关装饰器目标的详细信息。

装饰器的目标取决于我们添加它们的位置。 目前,装饰器可以添加到类的以下组件中:

例如,假设我们有一个名为 seal 的装饰器,它在类中调用 Object.seal。 要使用我们的装饰器,我们可以编写以下内容:

这同样适用于所有其他类型的装饰器:

要添加多个装饰器,请将它们一个接一个地添加在一起:

在 TypeScript 中创建类装饰器

在本节中,我们将完成在 TypeScript 中创建类装饰器的步骤。

对于名为 @decoratorA 的装饰器,我们告诉 TypeScript 它应该调用函数 decoratorA。 将调用 decoratorA 函数,其中包含有关如何在代码中使用装饰器的详细信息。

要创建自己的装饰器,我们必须创建一个与装饰器同名的函数。 也就是说,要创建您在上一节中看到的密封类装饰器,您必须创建一个接收一组特定参数的密封函数。 让我们这样做:

传递给装饰器的参数将取决于装饰器的使用位置。第一个参数通常称为目标。

然后,在密封函数中,在目标(即类构造函数)以及它们的原型上调用 Object.seal。当这样做时,不能将新属性添加到类构造函数或其属性中,并且现有属性将被标记为不可配置。

重要的是要记住,目前在使用装饰器时无法扩展目标的 TypeScript 类型。这意味着,例如,你无法使用装饰器将新字段添加到类并使其成为类型安全的。

如果在密封类装饰器中返回了一个值,该值将成为该类的新构造函数。如果想完全覆盖类构造函数,这很有用。

已经创建了第一个装饰器,并将它与一个类一起使用。

接下来,我们将学习如何创建装饰器工厂。

创建装饰器工厂

有时,我们需要在应用装饰器时将其他选项传递给装饰器,为此,我们必须使用装饰器工厂。

在这里,我们将学习如何创建和使用这些工厂。

装饰器工厂是返回另一个函数的函数。他们收到这个名字是因为他们不是装饰器实现本身。

相反,它们返回另一个负责实现装饰器的函数并充当包装函数。通过允许客户端代码在使用装饰器时将选项传递给装饰器,它们在使装饰器可定制方面很有用。

假设,有一个名为 decoratorA 的类装饰器,并且,我们想添加一个可以在调用装饰器时设置的选项,例如,布尔标志,可以通过编写类似于以下的装饰器工厂来实现此目的:

在这里,decoratorA 函数返回另一个带有装饰器实现的函数。 注意,装饰器工厂如何接收一个布尔标志作为它的唯一参数:

我们可以在使用装饰器时传递此参数的值。

请参阅以下示例中突出显示的代码:

在这里,当我们使用 decoratorA 装饰器时,将调用装饰器工厂,并将 someBooleanFlag 参数设置为 true。

然后,装饰器实现本身将运行。 这允许我们根据使用方式更改装饰器的行为,从而,使我们的装饰器易于自定义和通过应用程序重用。

请注意,我们需要传递装饰器工厂预期的所有参数。 如果,我们只是应用装饰器而不传递任何参数,如下例所示:

TypeScript 编译器会给你两个错误,这可能会因装饰器的类型而异。 对于类装饰器,错误是 1238 和 1240:

我们刚刚创建了一个能够接收参数并根据这些参数更改其行为的装饰器工厂。

在下一步中,我们将学习如何创建属性装饰器。

创建属性装饰器

类属性是另一个可以使用装饰器的地方,在这里,我们将了解如何创建它们。

任何属性装饰器都接收以下参数:

目前,没有办法获取属性描述符作为参数。 这是由于 TypeScript 中属性装饰器的初始化方式。

这是一个装饰器函数,它将成员的名称打印到控制台:

当我们运行上面的 TypeScript 代码时,你会在控制台中看到如下打印:

我们可以使用属性装饰器来覆盖被装饰的属性。这可以通过Object.defineProperty与属性的新 setter 和 getter 一起使用来完成。

让我们看看如何创建一个名为 的装饰器allowlist,它只允许将属性设置为静态允许列表中存在的值:

首先,我们要在代码顶部创建一个静态许可名单:

然后,我们创建一个属性装饰器:

请注意,我们如何使用 any 作为目标的类型:

对于属性装饰器来说,目标参数的类型可以是类的构造函数,也可以是类的原型,在这种情况下使用any比较容易。

在装饰器实现的第一行中,我们将被装饰的属性的当前值存储到 currentValue 变量中:

对于静态属性,这将设置为其默认值(如果有)。

对于非静态属性,这将始终未定义。 这是因为在运行时,在编译的 JavaScript 代码中,装饰器在实例属性设置为其默认值之前运行。

然后,我们将使用 Object.defineProperty 覆盖该属性:

Object.defineProperty 调用有一个 getter 和一个 setter。 getter 返回存储在 currentValue 变量中的值。

如果 currentVariable 在允许列表中,setter 会将其值设置为 newValue。

让我们使用您刚刚编写的装饰器。 创建以下 Person 类:

我们现在将创建类的新实例,并测试设置并获取name实例属性:

运行代码,我们应该看到以下输出:

该值永远不会设置为 Peter,因为 Peter 不在允许列表中。

如果我们想让代码更具可重用性,允许在应用装饰器时设置允许列表,该怎么办? 这是装饰器工厂的一个很好的用例。

让我们通过 allowlistOnly 装饰器变成装饰器工厂来做到这一点。

在这里,我们将之前的实现包装到另一个函数中,即装饰器工厂。 装饰器工厂接收一个名为允许列表的参数,它是一个字符串数组。

现在,要使用的装饰器,我们必须通过许可名单,如以下突出显示的代码所示:

尝试运行与之前编写的代码类似的代码,但有新的更改:

输出如下:

显示它按预期工作,person.name 永远不会设置为 Peter,因为 Peter 不在给定的白名单中。

现在,我们已经使用普通装饰器函数和装饰器工厂创建了第一个属性装饰器,是时候看看如何为类访问器创建装饰器了。

创建访问器装饰器

在这里,我们将了解装饰类访问器。

就像属性装饰器一样,访问器中使用的装饰器接收以下参数:

但与属性装饰器不同的是,它还接收第三个参数,即访问器成员的属性描述符。

鉴于 Property Descriptors 包含特定成员的 setter 和 getter,访问器装饰器只能应用于单个成员的 setter 或 getter,而不能同时应用于两者。

如果我们从访问器装饰器返回一个值,该值将成为 getter 和 setter 成员的访问器的新属性描述符。

下面是一个可用于更改 getter/setter 访问器的可枚举标志的装饰器示例:

请注意示例中,我们是如何使用装饰器工厂的。 这允许我们在调用装饰器时指定可枚举标志。

以下是如何使用装饰器:

访问器装饰器类似于属性装饰器。 唯一的区别是它们接收带有属性描述符的第三个参数。 现在,我们已经创建了第一个访问器装饰器。

接下来,我们将学习如何创建方法装饰器。

创建方法装饰器

在这里,我们将学习如何使用方法装饰器。

方法装饰器的实现与创建访问器装饰器的方式非常相似。 传递给装饰器实现的参数与传递给访问器装饰器的参数相同。

让我们重用之前创建的同一个可枚举装饰器,但这次是在以下 Person 类的 getFullName 方法中:

如果我们从方法装饰器返回一个值,该值将成为该方法的新属性描述符。

让我们创建一个deprecated的装饰器,它在使用该方法时将传递的消息打印到控制台,记录一条消息说该方法已被弃用:

在这里,我们正在使用装饰器工厂创建装饰器。 这个装饰器工厂接收一个字符串类型的参数,这是弃用的原因,如下面突出显示的部分所示:

deprecationReason 将在稍后将弃用消息记录到控制台时使用。在不推荐使用装饰器的实现中,我们正在返回一个值。当我们从方法装饰器返回值时,该值将覆盖该成员的属性描述符。

我们正在利用这一点为装饰类方法添加一个吸气剂。这样,我们就可以更改方法本身的实现。

但是为什么不直接使用 Object.defineProperty 而不是为方法返回一个新的属性装饰器呢?这是必要的,因为,我们需要访问 this 的值,对于非静态类方法,它绑定到类实例。

如果,我们直接使用 Object.defineProperty ,将无法检索 this 的值,并且如果该方法以任何方式使用 this ,则当从装饰器实现中运行包装的方法时,装饰器会破坏我们的代码。

在这样情况下,getter 本身的 this 值绑定到非静态方法的类实例,并绑定到静态方法的类构造函数。

然后,在你的 getter 中创建一个本地包装函数,称为 wrapperFn,此函数使用 console.warn 将消息记录到控制台,传递从装饰器工厂收到的 deprecationReason,然后使用 propertyDescriptor.value 调用原始方法。

apply(this, args),以这种方式调用原始方法,并将其 this 值正确绑定到类实例,以防它是非静态方法。

然后,我们将使用 defineProperty 覆盖类中方法的值。这就像一种记忆机制,因为对同一方法的多次调用将不再调用 getter,而是直接调用 wrapperFn。

我们现在正在使用 Object.defineProperty 将类中的成员设置为将wrapperFn 作为其值。

让我们使用已弃用的装饰器:

在这里,我们创建了一个具有两个属性的 TestClass:一个是静态的,一个是非静态的。 我们还创建了两种方法:一种是静态的,一种是非静态的。

然后,我们将已弃用的装饰器应用于这两种方法。 运行代码时,控制台中会出现以下内容:

这表明这两种方法都使用了包装函数正确包装,该函数将一条消息记录到控制台并说明弃用原因。

你现在已经使用 TypeScript 创建了你的第一个方法装饰器。

接下来,我们将学习如何创建 TypeScript 支持的最后一个装饰器类型,即参数装饰器。

创建参数装饰器

参数装饰器可以用在类方法的参数中。

在这里,我们将学习如何创建一个与参数一起使用的装饰器函数,

接收以下参数:

方法参数列表中参数的索引。

无法更改与参数本身相关的任何内容,因此,此类装饰器仅对观察参数使用本身有用(除非您使用更高级的东西,例如反射元数据)。

这是一个装饰器的示例,它打印被装饰的参数的索引以及方法名称:

然后,你可以像这样使用你的参数装饰器:

运行上述代码应在控制台中显示以下内容:

我们现在已经创建并执行了一个参数装饰器,并打印出返回装饰参数索引的结果。

总结

在本教程中,我们已经实现了 TypeScript 支持的所有装饰器,将它们与类一起使用,并了解了它们之间的区别。

现在可以开始编写自己的装饰器来减少代码库中的样板代码,或者更加自信地使用带有库(例如 Mobx)的装饰器。

以上就是我跟你分享的全部内容,如果你觉得有用,请记得分享给你身边的朋友,也许能够帮助到他。

面向对象,Object Oriented,软件开发方法,一种编程范式。

面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。

面向对象的三大特性:封装、继承、多态。

TypeScript面向对象,类 (class)

1、static关键字,表示一个静态属性,通过类访问。

2、readonly关键字,表示一个只读属性,不能修改属性,构造函数可初始化。

构造函数和this关键字

1、constructor函数,称为构造函数,对象创建时调用。

2、在实例方法中,this就表示当前当前的实例。

继承(extends)和super关键字

1、子类写了constructor构造函数必须使用super继承父类constructor构造函数的属性

2、通过继承可以将多个类中共有的代码写在一个父类中,继承后子类将会拥有父类所有的方法和属性。

3、方法重写,子类中添加了和父类相同的方法,子类方法会覆盖掉父类的方法

抽象(abstract)

1、以abstract开头的类是抽象类,抽象类不能实例化,事专门用来被继承的类。

2、抽象类中可以添加抽象方法,非抽象类中不可以有抽象方法。

3、抽象方法使用abstract开头,没有方法体,只能定义在抽象类中,子类必须对抽象方法进行重写。

接口(interface)

1、接口的作用类似于抽象类,不同点在于接口中的所有函数和属性都是没有实值的,接口中的所有方法都是抽象方法。

2、接口主要负责定义一个类的结构,限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。

3、一个类去实现接口,实现接口时类中要包括接口中的所有属性。

封装和属性的封装

1、private修饰符,私有的属性,私有属性只能在类内部进行访问修改。

2、protected修饰符,受保护的属性,只能在当前类和当前类的子类中访问。

3、public修饰符,共有的属性,修饰的属性可以再任意位置访问修改默认值。

4、getter方法用来读取属性。

5、setter方法迎来设置属性。

6、getter和setter被统一称为属性的存储器,定义时在方法之前添加get和set,调用的时候直接通过点语法调用。

案例1:

案例2:

案例3:

案例4:

案例5:

介绍

TypeScript 是 JavaScript 语言的扩展,它使用 JavaScript 运行时和编译时类型检查器。

这种组合允许开发人员使用完整的 JavaScript 生态系统和语言功能,同时还添加可选的静态类型检查、枚举数据类型、类和接口。这些特性为开发人员提供了 JavaScript 动态特性的灵活性,但也允许更可靠的代码库,其中可以在编译时使用类型信息来检测可能在运行时导致错误或其他意外行为的问题。

额外的类型信息还提供了更好的代码库文档,并在文本编辑器中改进了 IntelliSense(代码完成、参数信息和类似的内容辅助功能)。队友可以准确地确定任何变量或函数参数的预期类型,而无需通过实现本身。

准备工作

要遵循本教程,我们将需要:

1)、一个环境,我们可以在其中执行 TypeScript 程序以跟随示例。要在本地计算机上进行设置,我们将需要以下内容。

2)、如果你不想在本地机器上创建 TypeScript 环境,你可以使用官方的 TypeScript Playground 来跟随。

3)、我们将需要足够的 JavaScript 知识,尤其是 ES6+ 语法,例如解构、rest 运算符和导入/导出。有关JavaScript的更多主题信息,建议阅读我们的 JavaScript 系列教程。

4)、本教程将参考支持 TypeScript 并显示内联错误的文本编辑器的各个方面。这不是使用 TypeScript 所必需的,但确实可以更多地利用 TypeScript 功能。为了获得这些好处,我们可以使用像 Visual Studio Code 这样的文本编辑器,它完全支持开箱即用的 TypeScript。我们也可以在 TypeScript Playground 中尝试这些好处。

本教程中显示的所有示例都是使用 TypeScript 4.2.2 版创建的。

创建自定义类型

自定义类型语法

在 TypeScript 中,创建自定义类型的语法是使用 type 关键字,后跟类型名称,然后使用类型属性分配给 {} 块。采取以下措施:

语法类似于对象文字,其中键是属性的名称,值是该属性应具有的类型。这定义了一个 Programmer 类型,它必须是一个对象,其 name 键保存一个字符串值,并且 knownFor 键保存一个字符串数组。

如前面的示例所示,我们可以使用 作为每个属性之间的分隔符。也可以使用逗号、, 或完全省略分隔符,如下所示:

使用自定义类型与使用任何基本类型相同。添加一个双冒号,然后添加我们的类型名称:

ada 常量现在将通过类型检查器而不会引发错误。

如果我们在任何完全支持 TypeScript 的编辑器中编写此示例,例如在 TypeScript Playground 中,编辑器将建议该对象期望的字段及其类型,如下面的动画所示:

如果我们使用 TSDoc 格式(一种流行的 TypeScript 注释文档样式)向字段添加注释,则在代码完成中也建议使用它们。使用以下代码并在注释中进行解释:

注释描述现在将与字段建议一起出现:

TypeScript 编译器 (tsc) 将显示错误 2322:

如果我们省略了我们的类型所需的任何属性,如下所示:

TypeScript 编译器将给出错误 2741:

添加原始类型中未指定的新属性也会导致错误:

在这种情况下,显示的错误是 2322:

嵌套自定义类型

我们还可以将自定义类型嵌套在一起。想象一下,我们有一个 Company 类型,它有一个符合 Person 类型的 manager 字段。我们可以像这样创建这些类型:

然后,我们可以像这样创建一个 Company 类型的值:

我们可以省略 manager 常量中的类型,因为它与 Person 类型具有相同的形状。当我们使用与 manager 属性类型所期望的形状相同的对象时,TypeScript 不会引发错误,即使它没有明确设置为 Person 类型。

以下不会引发错误:

我们甚至可以更进一步,直接在company对象字面量中设置manager:

所有这些场景都是有效的。

如果在支持 TypeScript 的编辑器中编写这些示例,我们会发现编辑器将使用可用的类型信息来记录自己。对于前面的示例,只要我们打开 manager 的 {} 对象文字,编辑器就会期望一个name类型的字符串属性:

现在,我们已经完成了一些使用固定数量的属性创建我们自己的自定义类型的示例,接下来,我们将尝试向我们的类型添加可选属性。

可选属性

要将可选属性添加到类型,请添加 ? 属性的修饰符。使用前面部分中的 Programmer 类型,通过添加以下突出显示的字符将 knownFor 属性转换为可选属性:

在这里我们要添加 ? 属性名称后的修饰符。这使得 TypeScript 将此属性视为可选的,并且在我们省略该属性时不会引发错误:

这将毫无错误地通过。

既然,我们已经知道如何向类型添加可选属性,那么,现在该学习如何创建一个可以容纳无限数量的字段的类型了。

可索引类型

在这里,我们使用大括号 ({}) 中的类型定义块创建一个普通类型,然后以 [key: typeOfKeys]: typeOfValues 的格式添加一个特殊属性,其中 typeOfKeys 是该对象的键应具有的类型, typeOfValues 是这些键的值应该具有的类型。

然后,我们可以像任何其他类型一样正常使用它:

使用可索引类型,我们可以分配无限数量的属性,只要它们与索引签名匹配,索引签名是用于描述可索引类型的键和值的类型的名称。在这种情况下,键具有字符串类型,值具有任何类型。

还可以将始终需要的特定属性添加到可索引类型中,就像使用普通类型一样。在以下突出显示的代码中,我们将状态属性添加到我们的数据类型:

这意味着数据类型对象必须有一个带有布尔值的状态键才能通过类型检查器。

现在,我们可以创建具有不同数量元素的对象,我们可以继续学习 TypeScript 中的数组,它可以具有自定义数量的元素或更多。

创建元素数量或更多的数组

使用 TypeScript 中可用的数组和元组基本类型,我们可以为应该具有最少元素的数组创建自定义类型。在本节中,我们将使用 TypeScript 剩余运算符...来执行此 *** 作。

想象一下,我们有一个负责合并多个字符串的函数。此函数将采用单个数组参数。这个数组必须至少有两个元素,每个元素都应该是字符串。我们可以使用以下内容创建这样的类型:

MergeStringsArray 类型利用了这样一个事实,即我们可以将 rest 运算符与数组类型一起使用,并将其结果用作元组的第三个元素。这意味着前两个字符串是必需的,但之后的其他字符串元素不是必需的。

如果一个数组的字符串元素少于两个,它将是无效的,如下所示:

TypeScript 编译器在检查此数组时将给出错误 2322:

到目前为止,我们已经从基本类型的组合中创建了自己的自定义类型。在下一节中,我们将通过将两个或多个自定义类型组合在一起来创建一个新类型。

组合类型

在这里我们将介绍两种组合类型的方法。这些将使用联合运算符传递符合一种或另一种类型的任何数据,并使用交集运算符传递满足两种类型中所有条件的数据。

Unions

unions是使用 | 创建的 (pipe) 运算符,它表示可以具有联合中任何类型的值。举个例子:

在此代码中,ProductCode 可以是字符串或数字。以下代码将通过类型检查器:

unions类型可以从任何有效 TypeScript 类型的联合中创建。

Intersections

我们可以使用相交类型来创建一个全新的类型,该类型具有相交在一起的所有类型的所有属性。

例如,假设我们有一些公共字段始终出现在 API 调用的响应中,然后是某些端点的特定字段:

在这种情况下,所有响应都将具有 status 和 isValid 属性,但只有用户响应将具有附加的用户字段。要使用交集类型创建特定 API 用户调用的结果响应,请结合使用 StatusResponse 和 GetUserResponse 类型:

ApiGetUserResponse 类型将具有 StatusResponse 中可用的所有属性以及 GetUserResponse 中可用的属性。这意味着数据只有在满足两种类型的所有条件时才会通过类型检查器。以下示例将起作用:

另一个示例是数据库客户端为包含连接的查询返回的行类型。我们将能够使用交集类型来指定此类查询的结果:

稍后,如果我们使用 fetchRowsFromDatabase() 函数,如下所示:

生成的常量joinedRows 必须有一个role 属性和一个name 属性,它们都保存字符串值,以便通过类型检查器。

使用模板字符串类型

从 TypeScript 4.1 开始,可以使用模板字符串类型创建类型。这将允许我们创建检查特定字符串格式的类型,并为我们的 TypeScript 项目添加更多自定义。

要创建模板字符串类型,我们使用的语法与创建模板字符串文字时使用的语法几乎相同。但是,我们将在字符串模板中使用其他类型而不是值。

想象一下,我们想创建一个传递所有以 get 开头的字符串的类型。我们可以使用模板字符串类型来做到这一点:

myString 将在此处通过类型检查器,因为字符串以 get 开头,然后是一个附加字符串。

如果我们将无效值传递给我们的类型,例如以下 invalidStringValue:

TypeScript 编译器会给我们错误 2322:

使用模板字符串创建类型可帮助我们根据项目的特定需求自定义类型。在下一节中,我们将尝试类型断言,它为其他无类型数据添加类型。

Using Type Assertions

如果我们想让我们的代码在这些场景中是类型安全的,我们可以使用类型断言,这是一种将变量类型更改为另一种类型的方法。通过在变量后添加 as NewType 可以实现类型断言。这会将变量的类型更改为 as 关键字之后指定的类型。

举个例子:

value 的类型为 any,但是,使用 as 关键字,此代码将 value 强制为 string 类型。

注意:要断言 TypeA 的变量具有 TypeB 类型,TypeB 必须是 TypeA 的子类型。几乎所有的 TypeScript 类型,除了 never,都是 any 的子类型,包括 unknown。

实用程序类型

在前面的部分中,我们查看了从基本类型创建自定义类型的多种方法。但有时我们不想从头开始创建一个全新的类型。有时最好使用现有类型的一些属性,甚至创建一个与另一种类型具有相同形状但所有属性都设置为可选的新类型。

使用 TypeScript 提供的现有实用程序类型,所有这些都是可能的。本节将介绍其中一些实用程序类型;有关所有可用的完整列表,请查看 TypeScript 手册的实用程序类型部分。

所有实用程序类型都是通用类型,我们可以将其视为接受其他类型作为参数的类型。可以通过使用 语法向其传递类型参数来识别通用类型。

Record

Record 实用程序类型可用于以比使用之前介绍的索引签名更简洁的方式创建可索引类型。

在我们的可索引类型示例中,我们具有以下类型:

我们可以使用 Record 实用程序类型而不是像这样的可索引类型:

Record 泛型的第一个类型参数是每个键的类型。在以下示例中,所有键都必须是字符串:

第二个类型参数是这些键的每个值的类型。以下将允许值是任何值:

Omit

Omit 实用程序类型可用于基于另一种类型创建新类型,同时排除结果类型中不需要的一些属性。

假设我们有以下类型来表示数据库中用户行的类型:

如果在我们的代码中,我们要检索除 addressId 之外的所有字段,则可以使用 Omit 创建没有该字段的新类型:

Omit 的第一个参数是新类型所基于的类型。第二个是我们要省略的字段。

如果我们在代码编辑器中将鼠标悬停在 UserRowWithoutAddressId 上,我们会发现它具有 UserRow 类型的所有属性,但我们省略了这些属性。

我们可以使用字符串联合将多个字段传递给第二个类型参数。假设我们还想省略 id 字段,我们可以这样做:

Pick

Pick 实用程序类型与 Omit 类型完全相反。我们无需说出要省略的字段,而是指定要从其他类型使用的字段。

使用我们之前使用的相同 UserRow:

假设我们只需要从数据库行中选择电子邮件键。我们可以像这样使用 Pick 创建这样的类型:

Pick 这里的第一个参数指定了新类型所基于的类型。第二个是我们想要包含的键。

这将等同于以下内容:

我们还可以使用字符串联合来选择多个字段:

Partial

使用相同的 UserRow 示例,假设我们想创建一个新类型,该类型与我们的数据库客户端可以用来将新数据插入用户表中的对象相匹配,但有一个小细节:我们的数据库具有所有字段的默认值,所以,我们是不需要通过其中任何一个。

为此,我们可以使用 Partial 实用程序类型来选择性地包括基本类型的所有字段。

我们现有的类型 UserRow 具有所需的所有属性:

要创建所有属性都是可选的新类型,我们可以使用 Partial 实用程序类型,如下所示:

这与拥有这样的 UserRowInsert 完全相同:

实用程序类型是一个很好的资源,因为它们提供了一种比从 TypeScript 中的基本类型创建类型更快的方法来构建类型。

总结

创建我们自己的自定义类型来表示我们自己的代码中使用的数据结构,可以为我们的项目提供灵活且有用的 TypeScript 解决方案。除了从整体上提高我们自己代码的类型安全性之外,将我们自己的业务对象类型化为代码中的数据结构将增加代码库的整体文档,并在与团队成员一起工作时改善我们自己的开发人员体验相同的代码库。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11500719.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-16
下一篇 2023-05-16

发表评论

登录后才能评论

评论列表(0条)

保存