TypeScript 入门指南

TypeScript 入门指南,第1张

新系列 深入浅出TypeScript 来了,本系列至少20+篇。本文为第一篇,来介绍一下TypeScript 以及常见的类型

TypeScript是一门由微软推出的开源的、跨平台的编程语言。它是JavaScript的超集,扩展了 JavaScript 的语法,最终会被编译为JavaScript代码

TypeScript的主要特性:

TypeScript 主要是为了实现以下两个目标:

下面就来看看这两个目标是如何实现的。

为什么要给JavaScript加上类型呢?

我们知道,JavaScript是一种轻量级的解释性脚本语言。也是弱类型、动态类型语言,允许隐式转换,只有运行时才能确定变量的类型。正是因为在运行时才能确定变量的类型,JavaScript代码很多错误在运行时才能发现。TypeScript在JavaScript的基础上,包装了类型机制,使其变身成为 静态类型 语言。在 TypeScript 中,不仅可以轻易复用 JavaScript 的代码、最新特性,还能使用可选的静态类型进行检查报错,使得编写的代码更健壮、更易于维护。

下面是 JavaScript 项目中最常见的十大错误,如果使用 TypeScript,那么在 编写阶段 就可以发现并解决很多 JavaScript 错误了:

类型系统能够提高代码的质量和可维护性,经过不断的实践,以下两点尤其需要注意:

可以认为,在所有 *** 作符之前,TypeScript 都能检测到接收的类型(在代码运行时, *** 作符接收的是实际数据;在静态检测时, *** 作符接收的则是类型)是否被当前 *** 作符所支持。当 TypeScript 类型检测能力覆盖到所有代码后,任意破坏约定的改动都能被自动检测出来,并提出类型错误。因此,可以放心地修改、重构业务逻辑,而不用担忧因为考虑不周而犯下低级错误。

在一些语言中,类型总是有一些不必要的复杂的存在方式,而 TypeScript 尽可能地降低了使用门槛,它是通过如下方式来实现的。

TypeScript 与 JavaScript 本质并无区别,我们可以将 TypeScipt 理解为是一个添加了类型注解的 JavaScript,为JavaScript代码提供了编译时的类型安全。

实际上,TypeScript 是一门“ 中间语言 ”,因为它最终会转化为JavaScript,再交给浏览器解释、执行。不过 TypeScript 并不会破坏 JavaScript 原有的体系,只是在 JavaScript 的基础上进行了扩展。

准确的说,TypeScript 只是将JavaScript中的方法进行了标准化处理:

这段代码在TypeScript中就会报错,因为TS会知道a是一个数字类型,不能将其他类型的值赋值给a,这种类型的推断是很有必要的。

上面说了,TypeScript会尽可能安全的推断类型。我们也可以使用类型注释,以实现以下两件事:

在一些语言中,类型总是有一些不必要的复杂的存在方式,而 TypeScript 的类型是结构化的。比如下面的例子中,函数会接受它所期望的参数:

为了便于把 JavaScript 代码迁移至 TypeScript,即使存在编译错误,在默认的情况下,TypeScript 也会尽可能的被编译为 JavaScript 代码。因此,我们可以将JavaScript代码逐步迁移至 TypeScript。

虽然 TypeScript 是 JavaScript 的超集,但它始终紧跟ECMAScript标准,所以是支持ES6/7/8/9 等新语法标准的。并且,在语法层面上对一些语法进行了扩展。TypeScript 团队也正在积极的添加新功能的支持,这些功能会随着时间的推移而越来越多,越来越全面。

虽然 TypeScript 比较严谨,但是它并没有让 JavaScript 失去其灵活性。TypeScript 由于兼容 JavaScript 所以灵活度可以媲美 JavaScript,比如可以在任何地方将类型定义为 any(当然,并不推荐这样使用),毕竟 TypeScript 对类型的检查严格程度是可以通过 tsconfig.json 来配置的。

在搭建TypeScript环境之前,先来看看适合TypeScript的IDE,这里主要介绍Visual Studio Code,笔者就一直使用这款编辑器。

VS Code可以说是微软的亲儿子了,其具有以下优势:

因为 VS Code 中内置了特定版本的 TypeScript 语言服务,所以它天然支持 TypeScript 语法解析和类型检测,且这个内置的服务与手动安装的 TypeScript 完全隔离。因此, VS Code 支持在内置和手动安装版本之间动态切换语言服务,从而实现对不同版本的 TypeScript 的支持。

如果当前应用目录中安装了与内置服务不同版本的 TypeScript,我们就可以点击 VS Code 底部工具栏的版本号信息,从而实现 “use VS Code's Version” 和 “use Workspace's Version” 两者之间的随意切换。

除此之外,VS Code 也基于 TypeScript 语言服务提供了准确的代码自动补全功能,并显示详细的类型定义信息,大大的提升了我们的开发效率。

1)全局安装TypeScript:

2)初始化配置文件:

执行之后,项目根目录会出现一个 tsconfig.json 文件,里面包含ts的配置项(可能因为版本不同而配置略有不同)。

可以在 package.json 中加入script命令:

3)编译ts代码:

TSLint 是一个通过 tslint.json 进行配置的插件,在编写TypeScript代码时,可以对代码风格进行检查和提示。如果对代码风格有要求,就需要用到TSLint了。其使用步骤如下: (1)在全局安装TSLint:

(2)使用TSLint初始化配置文件:

执行之后,项目根目录下多了一个 tslint.json 文件,这就是TSLint的配置文件了,它会根据这个文件对代码进行检查,生成的 tslint.json 文件有下面几个字段:

这些字段的含义如下;

在说TypeScript数据类型之前,先来看看在TypeScript中定义数据类型的基本语法。

在语法层面,缺省类型注解的 TypeScript 与 JavaScript 完全一致。因此,可以把 TypeScript 代码的编写看作是为 JavaScript 代码添加类型注解。

在 TypeScript 语法中,类型的标注主要通过类型后置语法来实现:“ 变量: 类型

在 JavaScript 中,原始类型指的是 非对象且没有方法 的数据类型,包括:number、boolean、string、null、undefined、symbol、bigInt。

它们对应的 TypeScript 类型如下:

JavaScript原始基础类型TypeScript类型 numbernumber booleanboolean stringstring nullnull undefinedundefined symbolsymbol bigIntbigInt

需要注意 number Number 的区别:TypeScript中指定类型的时候要用 number ,这是TypeScript的类型关键字。而 Number 是 JavaScript 的原生构造函数,用它来创建数值类型的值,这两个是不一样的。包括 string boolean 等都是TypeScript的类型关键字,而不是JavaScript语法。

TypeScript 和 JavaScript 一样,所有数字都是 浮点数 ,所以只有一个 number 类型。

TypeScript 还支持 ES6 中新增的二进制和八进制字面量,所以 TypeScript 中共支持 2、8、10和16 这四种进制的数值:

字符串类型可以使用单引号和双引号来包裹内容,但是如果使用 Tslint 规则,会对引号进行检测,使用单引号还是双引号可以在 Tslint 规则中进行配置。除此之外,还可以使用 ES6 中的模板字符串来拼接变量和字符串会更为方便。

类型为布尔值类型的变量的值只能是true或者false。除此之外,赋值给布尔值的值也可以是一个计算之后结果为布尔值的表达式:

在 JavaScript 中,undefined和 null 是两个基本数据类型。在 TypeScript 中,这两者都有各自的类型,即 undefined 和 null,也就是说它们既是实际的值,也是类型。这两种类型的实际用处不是很大。

注意,第一行代码可能会报一个tslint的错误: Unnecessary initialization to 'undefined' ,就是不能给一个变量赋值为undefined。但实际上给变量赋值为undefined是完全可以的,所以如果想让代码合理化,可以配置tslint,将" no-unnecessary-initializer "设置为 false 即可。

默认情况下,undefined 和 null 是所有类型的子类型,可以赋值给任意类型的值,也就是说可以把 undefined 赋值给 void 类型,也可以赋值给 number 类型。当在 tsconfig.json 的"compilerOptions"里设置为 "strictNullChecks": true 时,就必须严格对待了。这时 undefined 和 null 将只能赋值给它们自身或者 void 类型。这样也可以规避一些错误。

BigInt是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示大于 2- 1 的整数,BigInt可以表示任意大的整数。

使用 BigInt 可以安全地存储和 *** 作大整数,即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。

我们知道,在 JavaScript 中采用双精度浮点数,这导致精度有限,比如 Number.MAX_SAFE_INTEGER 给出了可以安全递增的最大可能整数,即 2- 1 ,来看一个例子:

可以看到,最终返回了true,这就是超过精读范围造成的问题,而 BigInt 正是解决这类问题而生的:

这里需要用 BigInt(number) 把 Number 转化为 BigInt ,同时如果类型是 BigInt ,那么数字后面需要加 n 。

在TypeScript中, number 类型虽然和 BigInt 都表示数字,但是实际上两者类型是完全不同的:

symbol我们平时用的比较少,所以可能了解也不是很多,这里就详细来说说symbol。

symbol 是 ES6 新增的一种基本数据类型,它用来表示独一无二的值,可以通过 Symbol 构造函数生成。

注意:Symbol 前面不能加 new关键字,直接调用即可创建一个独一无二的 symbol 类型的值。

可以在使用 Symbol 方法创建 symbol 类型值的时候传入一个参数,这个参数需要是一个字符串。如果传入的参数不是字符串,会先自动调用传入参数的 toString 方法转为字符串:

上面代码的第三行可能会报一个错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap. 这是因为编译器检测到这里的 s1 === s2 始终是false,所以编译器提醒这代码写的多余,建议进行优化。

上面使用Symbol创建了两个symbol对象,方法中都传入了相同的字符串,但是两个symbol值仍然是false,这就说明了 Symbol 方法会返回一个独一无二的值。Symbol 方法传入的这个字符串,就是方便我们区分 symbol 值的。可以调用 symbol 值的 toString 方法将它转为字符串:

在TypeScript中使用symbol就是指定一个值的类型为symbol类型:

在ES6中,对象的属性是支持表达式的,可以使用于一个变量来作为属性名,这对于代码的简化有很多用处,表达式必须放在大括号内:

symbol 也可以作为属性名,因为symbol的值是独一无二的,所以当它作为属性名时,不会与其他任何属性名重复。当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问):

在使用obj.name访问时,实际上是字符串name,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号。方括号中的name才是我们定义的symbol类型的变量name。

使用 Symbol 类型值作为属性名,这个属性是不会被 for…in遍历到的,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 、 JSON.stringify() 等方法获取到:

虽然这些方法都不能访问到Symbol类型的属性名,但是Symbol类型的属性并不是私有属性,可以使用 Object.getOwnPropertySymbols 方法获取对象的所有symbol类型的属性名:

除了这个方法,还可以使用ES6提供的 Reflect 对象的静态方法 Reflect.ownKeys ,它可以返回所有类型的属性名,Symbol 类型的也会返回:

Symbol 包含两个静态方法, for 和 keyFor 。 1)Symbol.for()

用Symbol创建的symbol类型的值都是独一无二的。使用 Symbol.for 方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值。如果有,返回该值;如果没有,则使用该字符串新创建一个。使用该方法创建 symbol 值后会在全局范围进行注册。

上面代码中,创建了一个iframe节点并把它放在body中,通过这个 iframe 对象的 contentWindow 拿到这个 iframe 的 window 对象,在 iframe.contentWindow上添加一个值就相当于在当前页面定义一个全局变量一样。可以看到,在 iframe 中定义的键为 TypeScript 的 symbol 值在和在当前页面定义的键为'TypeScript'的symbol 值相等,说明它们是同一个值。

2)Symbol.keyFor() 该方法传入一个 symbol 值,返回该值在全局注册的键名:

看完简单的数据类型,下面就来看看比较复杂的数据类型,包括JavaScript中的数组和对象,以及TypeScript中新增的元组、枚举、Any、void、never、unknown。

在 TypeScript 中有两种定义数组的方式:

以上两种定义数组类型的方式虽然本质上没有任何区别,但是更推荐使用第一种形式来定义。一方面可以避免与 JSX 语法冲突,另一方面可以减少代码量。

注意,这两种写法中的 number 指定的是数组元素的类型,也可以在这里将数组的元素指定为其他任意类型。如果要指定一个数组里的元素既可以是数值也可以是字符串,那么可以使用这种方式: number|string[] 。

在JavaScript中,object是引用类型,它存储的是值的引用。在TypeScript中,当想让一个变量或者函数的参数的类型是一个对象的形式时,可以使用这个类型:

可以看到,当给一个对象类型的变量赋值一个对象时,就会报错。对象类型更适合以下场景:

在 JavaScript 中并没有元组的概念,作为一门动态类型语言,它的优势是支持多类型元素数组。但是出于较好的扩展性、可读性和稳定性考虑,我们通常会把不同类型的值通过键值对的形式塞到一个对象中,再返回这个对象,而不是使用没有任何限制的数组。TypeScript 的元组类型正好弥补了这个不足,使得定义包含固定个数元素、每个元素类型未必相同的数组成为可能。

元组可以看做是数组的扩展,它表示已知元素数量和类型的数组,它特别适合用来实现多值返回。确切的说,就是已知数组中每一个位置上的元素的类型,可以通过元组的索引为元素赋值::

可以看到,定义的arr元组中,元素个数和元素类型都是确定的,当为arr赋值时,各个位置上的元素类型都要对应,元素个数也要一致。

当访问元组元素时,TypeScript也会对元素做类型检查,如果元素是一个字符串,那么它只能使用字符串方法,如果使用别的类型的方法,就会报错。

在TypeScript 新的版本中,TypeScript会对元组做越界判断。超出规定个数的元素称作越界元素,元素赋值必须类型和个数都对应,不能超出定义的元素个数。

这里定义了接口 Tuple ,它继承数组类型,并且数组元素的类型是 number 和 string 构成的联合类型,这样接口 Tuple 就拥有了数组类型所有的特性。并且指定索引为0的值为 string 类型,索引为1的值为 number 类型,同时指定 length 属性的类型字面量为 2,这样在指定一个类型为这个接口 Tuple 时,这个值必须是数组,而且如果元素个数超过2个时,它的length就不是2是大于2的数了,就不满足这个接口定义了,所以就会报错;当然,如果元素个数不够2个也会报错,因为索引为0或1的值缺失。

TypeScript 在 ES 原有类型基础上加入枚举类型,使得在 TypeScript 中也可以给一组数值赋予名字,这样对开发者比较友好。枚举类型使用enum来定义:

上面定义的枚举类型的Roles,它有三个值,TypeScript会为它们每个值分配编号,默认从0开始,在使用时,就可以使用名字而不需要记数字和名称的对应关系了:

除此之外,还可以修改这个数值,让SUPER_ADMIN = 1,这样后面的值就分别是2和3。当然还可以给每个值赋予不同的、不按顺序排列的值:

我们可以将一个值定义为any类型,也可以在定义数组类型时使用any来指定数组中的元素类型为任意类型:

any 类型会在对象的调用链中进行传导,即any 类型对象的任意属性的类型都是 any,如下代码所示:

需要注意:不要滥用any类型,如果代码中充满了any,那TypeScript和JavaScript就毫无区别了,所以除非有充足的理由,否则应该尽量避免使用 any ,并且开启禁用隐式 any 的设置。

void 和 any 相反,any 是表示任意类型,而 void 是表示没有类型,就是什么类型都不是。这在 定义函数,并且函数没有返回值时会用到

需要注意: void 类型的变量只能赋值为 undefined 和 null ,其他类型不能赋值给 void 类型的变量。

never 类型指永远不存在值的类型,它是那些 总会抛出异常 根本不会有返回值的函数表达式的返回值 类型,当变量被永不为真的类型保护所约束时,该变量也是 never 类型。

下面的函数,总是会抛出异常,所以它的返回值类型是never,用来表明它的返回值是不存在的:

never 类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是 never 的子类型,所以除了它自身以外,其他类型(包括 any 类型)都不能为 never 类型赋值。

上面代码定义了一个立即执行函数,函数体是一个死循环,这个函数调用后的返回值类型为 never,所以赋值之后 neverVariable 的类型是 never 类型,当给neverVariable 赋值 123 时,就会报错,因为除它自身外任何类型都不能赋值给 never 类型。

基于 never 的特性,我们可以把 never 作为接口类型下的属性类型,用来禁止 *** 作接口下特定的属性:

可以看到,无论给 props.name 赋什么类型的值,它都会提示类型错误,这就相当于将 name 属性设置为了只读 。

unknown 是TypeScript在3.0版本新增的类型,主要用来描述类型并不确定的变量。它看起来和any很像,但是还是有区别的,unknown相对于any更安全。

对于any,来看一个例子:

上面这些语句都不会报错,因为value是any类型,所以后面三个 *** 作都有合法的情况,当value是一个对象时,访问name属性是没问题的;当value是数值类型的时候,调用它的toFixed方法没问题;当value是字符串或数组时获取它的length属性是没问题的。

当指定值为unknown类型的时候,如果没有 缩小类型范围 的话,是不能对它进行任何 *** 作的。总之,unknown类型的值不能随便 *** 作。那什么是类型范围缩小呢?下面来看一个例子:

这里由于把value的类型缩小为Date实例的范围内,所以进行了value.toISOString(),也就是使用ISO标准将 Date 对象转换为字符串。

使用以下方式也可以缩小类型范围:

关于 unknown 类型,在使用时需要注意以下几点:

在实际使用中,如果有类型无法确定的情况,要尽量避免使用 any,因为 any 会丢失类型信息,一旦一个类型被指定为 any,那么在它上面进行任何 *** 作都是合法的,所以会有意想不到的情况发生。因此如果遇到无法确定类型的情况,要先考虑使用 unknown。

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)的装饰器。

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


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

原文地址: http://outofmemory.cn/yw/12000011.html

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

发表评论

登录后才能评论

评论列表(0条)

保存