装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

1️⃣ 类装饰器

类装饰器的本质就是一个函数,该函数接收一个参数,表示类本身( 构造函数本身 )
使用类装饰器 **@后跟一个函数**
在 TS 中如何约束一个变量为类

  1. function test(target: new () => object) {
  2. // target: new () => object 约束一个变量为类
  3. }

在 TS 中要使用装饰器,需要在配置中开启**experimentalDecorators**
装饰器的运行时间:在类定义后直接运行
类装饰器可以具有的返回值:

  1. 1. void:仅运行
  2. 1. 返回一个新的类
  1. function test(target: new () => object) {
  2. return class B extends target { // 返回一个新的类继承原来的类
  3. }
  4. }
  5. @test
  6. class A {
  7. }

2️⃣ TS 中如何约束一个构造函数

image.png
image.png

2️⃣ 装饰器的执行顺序

装饰器的正常执行顺序。
image.png
装饰器由函数返回的执行顺序。
image.png

1️⃣ 成员装饰器

2️⃣ 属性

属性装饰器也是一个函数,该函数需要两个参数:

  1. 1. 如果是静态属性,则为类本身;如果是实例属性,则为类的原型。
  2. 1. 固定为一个字符串,表示为属性名。

image.png

image.png

2️⃣ 方法

方法装饰器也是一个函数,该函数需要三个参数:

  1. 1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 1. 成员的名字。
  3. 1. 成员的属性描述符。

image.png

1️⃣ 保存元数据【 库 】

保存元数据

  1. 1. 使用 `reflect-metadata`
  2. 1. `npm install reflect-metadata`

image.png

1️⃣ 类验证【 库 】

类验证

  1. 使用 `class-validator` 库( `class-validator` 依赖于 `reflect-metadata` 库 )
  1. `npm install class-validator --save`

2️⃣ 使用方法

import "reflect-metadata" // class-validator 依赖于 reflect-metadata 所以需要引入
import { Max } from "class-validator"
class Post {
    @Max(10) // 最大为 10
  a: number;
}

2️⃣ validate 的返回对象数组

// 使用 validate() 函数传入类实例,通过 then 获取到错误信息
validate(post).then(error => {
  console.log(error);
})
// 该 validate 方法返回一个 ValidationError 对象数组。每个 ValidationError 是:
{
  target: Object; // 被验证的对象。
  property: string; // 未通过验证的对象属性。
  value: any; // 未通过验证的值。
  constraints ?: { // 验证失败并显示错误消息的约束。
    [type: string]: string;
  };
  children ?: ValidationError[]; // 包含属性的所有嵌套验证错误
}

如果您不想 **target **在验证错误中暴露 ,则在使用验证器时有一个特殊选项:

validator.validate(post, { validationError: { target: false } });

2️⃣ 验证消息

验证消息作为第二个参数对象的 key 传入 键为:message,值为:报错时返回的错误信息

class Post {
  @Contains('Hello', { message : '字符串不包含 Hello 字符'})  
  a: string = ''
}

您可以在消息中使用一些特殊令牌:

  1. **$value** - 正在验证的值
  1. **$property** - 正在验证的对象属性的名称
  1. **$target **- 被验证的对象类的名称
  1. **$constraint1, $constraint2, ... $constraintN**- 由特定验证类型定义的约束
class Post {
  @Contains('Hei', { message : '$value $property $target $constraint1'}) 
  a: string = 'HelloWorld';
}

// 报错信息返回为:
[
  ValidationError {
    target: Cla { title: 'HelloWorld' },
    value: 'HelloWorld',
    property: 'title',
    children: [],
    constraints: { contains: 'HelloWorld title Cla hello ' }
  }
]

您也可以提供一个返回消息的函数。这允许您创建更精细的消息:

import { MinLength, MaxLength, ValidationArguments } from 'class-validator';

class Post {
  @MinLength(10, {
    message: (args: ValidationArguments) => {
      if (args.value.length === 1) {
        return 'Too short, minimum length is 1 character';
      } else {
        return 'Too short, minimum length is ' + args.constraints[0] + ' characters';
      }
    },
  })
  title: string;
}

消息函数接受ValidationArguments其中包含以下信息:

  1. **value **- 正在验证的值
  1. **constraints** - 由特定验证类型定义的约束数组
  1. **targetName** - 被验证的对象类的名称
  1. **object **- 正在验证的对象
  1. **property** - 正在验证的对象属性的名称

2️⃣ 验证数组

如果您的字段是一个数组并且您想对数组中的每个项目执行验证,您必须指定一个特殊的 **each: true** 装饰器选项:
这将验证 post.tags 数组中的每个项目。

import { MinLength, MaxLength } from 'class-validator';

class Post {
  @Max(20, { // 数组的每一项都不能大于 20
    each: true,
  })
  tags: number[];
}

2️⃣ 验证装饰器

装饰器 描述
常见的验证装饰器
@IsDefined(value: any) 检查值是否已定义 (!== undefined, !== null)。这是唯一一个忽略 skipMissingProperties 选项的装饰器。
@IsOptional() 检查给定值是否为空(=== null,=== 未定义),如果是,则忽略该属性上的所有验证器。
@Equals(comparison: any) 检查值是否等于(“===”)比较。
@NotEquals(comparison: any) 检查值是否不相等 (“!==”) 比较。
@IsEmpty() 检查给定值是否为空 (=== ‘’, === null, === undefined)。
@IsNotEmpty() 检查给定值是否不为空 (!== ‘’, !== null, !== undefined)。
@IsIn(values: any[]) 检查值是否在允许值的数组中。
@IsNotIn(values: any[]) 检查 value 是否不在不允许的值数组中。
类型验证装饰器
@IsBoolean() 检查值是否为布尔值。
@IsDate() 检查该值是否为日期。
@IsString() 检查字符串是否为字符串。
@IsNumber(options: IsNumberOptions) 检查值是否为数字。
@IsInt() 检查值是否为整数。
@IsArray() 检查值是否为数组
@IsEnum(entity: object) 检查值是否是有效的枚举
数字验证装饰器
@IsDivisibleBy(num: number) 检查该值是否是一个可以被另一个整除的数字。
@IsPositive() 检查该值是否为大于零的正数。
@IsNegative() 检查值是否为小于零的负数。
@Min(min: number) 检查给定的数字是否大于或等于给定的数字。
@Max(max: number) 检查给定的数字是否小于或等于给定的数字。
日期验证装饰器
@MinDate(date: Date) 检查该值是否是指定日期之后的日期。
@MaxDate(date: Date) 检查该值是否是指定日期之前的日期。
字符串类型验证装饰器
@IsBooleanString() 检查字符串是否为布尔值(例如“真”或“假”)。
@IsDateString() 的别名@IsISO8601()
@IsNumberString(options?: IsNumericOptions) 检查字符串是否为数字。
字符串验证装饰器
@Contains(seed: string) 检查字符串是否包含种子。
@NotContains(seed: string) 检查字符串是否不包含种子。
@IsAlpha() 检查字符串是否仅包含字母 (a-zA-Z)。
@IsAlphanumeric() 检查字符串是否只包含字母和数字。
@IsDecimal(options?: IsDecimalOptions) 检查字符串是否为有效的十进制值。默认 IsDecimalOptions 是force_decimal=False
, decimal_digits: '1,'
,locale: 'en-US'
@IsAscii() 检查字符串是否仅包含 ASCII 字符。
@IsBase32() 检查字符串是否为 base32 编码。
@IsBase64() 检查字符串是否为 base64 编码。
@IsIBAN() 检查字符串是否为 IBAN(国际银行帐号)。
@IsBIC() 检查字符串是 BIC(银行识别码)还是 SWIFT 代码。
@IsByteLength(min: number, max?: number) 检查字符串的长度(以字节为单位)是否在一个范围内。
@IsCreditCard() 检查字符串是否是信用卡。
@IsCurrency(options?: IsCurrencyOptions) 检查字符串是否是有效的货币金额。
@IsEthereumAddress() 使用基本正则表达式检查字符串是否是以太坊地址。不验证地址校验和。
@IsBtcAddress() 检查字符串是否是有效的 BTC 地址。
@IsDataURI() 检查字符串是否为数据 uri 格式。
@IsEmail(options?: IsEmailOptions) 检查字符串是否是电子邮件。
@IsFQDN(options?: IsFQDNOptions) 检查字符串是否是完全限定的域名(例如 domain.com)。
@IsFullWidth() 检查字符串是否包含任何全角字符。
@IsHalfWidth() 检查字符串是否包含任何半角字符。
@IsVariableWidth() 检查字符串是否包含全角和半角字符的混合。
@IsHexColor() 检查字符串是否为十六进制颜色。
@IsHSLColor() 根据CSS 颜色级别 4 规范
检查字符串是否为 HSL 颜色。
@IsRgbColor(options?: IsRgbOptions) 检查字符串是 rgb 还是 rgba 颜色。
@IsIdentityCard(locale?: string) 检查字符串是否是有效的身份证代码。
@IsPassportNumber(countryCode?: string) 检查字符串是否是相对于特定国家/地区代码的有效护照号码。
@IsPostalCode(locale?: string) 检查字符串是否为邮政编码。
@IsHexadecimal() 检查字符串是否为十六进制数。
@IsOctal() 检查字符串是否为八进制数。
@IsMACAddress(options?: IsMACAddressOptions) 检查字符串是否为 MAC 地址。
@IsIP(version?: "4"|"6") 检查字符串是否为 IP(版本 4 或 6)。
@IsPort() 检查字符串是否是有效的端口号。
@IsISBN(version?: "10"|"13") 检查字符串是否为 ISBN(版本 10 或 13)。
@IsEAN() 检查字符串是否为 EAN(欧洲商品编号)。
@IsISIN() 检查字符串是否为 ISIN(股票/证券标识符)。
@IsISO8601(options?: IsISO8601Options) 检查字符串是否为有效的 ISO 8601 日期格式。使用选项 strict = true 对有效日期进行额外检查。
@IsJSON() 检查字符串是否是有效的 JSON。
@IsJWT() 检查字符串是否是有效的 JWT。
@IsObject() 检查对象是否为有效对象(null、函数、数组将返回 false)。
@IsNotEmptyObject() 检查对象是否不为空。
@IsLowercase() 检查字符串是否为小写。
@IsLatLong() 检查字符串是否是格式为 lat, long 的有效经纬度坐标。
@IsLatitude() 检查字符串或数字是否是有效的纬度坐标。
@IsLongitude() 检查字符串或数字是否是有效的经度坐标。
@IsMobilePhone(locale: string) 检查字符串是否为手机号码。
@IsISO31661Alpha2() 检查字符串是否是有效的 ISO 3166-1 alpha-2 官方指定的国家/地区代码。
@IsISO31661Alpha3() 检查字符串是否是有效的 ISO 3166-1 alpha-3 官方指定的国家/地区代码。
@IsLocale() 检查字符串是否为语言环境。
@IsPhoneNumber(region: string) 使用 libphonenumber-js 检查字符串是否是有效的电话号码。
@IsMongoId() 检查字符串是否是 MongoDB ObjectId 的有效十六进制编码表示。
@IsMultibyte() 检查字符串是否包含一个或多个多字节字符。
@IsNumberString(options?: IsNumericOptions) 检查字符串是否为数字。
@IsSurrogatePair() 检查字符串是否包含任何代理对字符。
@IsUrl(options?: IsURLOptions) 检查字符串是否为 url。
@IsMagnetURI() 检查字符串是否为磁铁 uri 格式
@IsUUID(version?: "3"|"4"|"5"|"all") 检查字符串是否为 UUID(版本 3、4、5 或全部)。
@IsFirebasePushId() 检查字符串是否为Firebase 推送 ID
@IsUppercase() 检查字符串是否为大写。
@Length(min: number, max?: number) 检查字符串的长度是否在一个范围内。
@MinLength(min: number) 检查字符串的长度是否不小于给定的数字。
@MaxLength(max: number) 检查字符串的长度是否不超过给定的数字。
@Matches(pattern: RegExp, modifiers?: string) 检查字符串是否与模式匹配。要么matches(‘foo’, /foo/i) 要么matches(‘foo’, ‘foo’, ‘i’)。
@IsMilitaryTime() 检查字符串是否是格式为 HH:MM 的军事时间的有效表示。
@IsHash(algorithm: string) 检查字符串是否为散列 支持以下类型:md4
, md5
, sha1
, sha256
, sha384
, sha512
, ripemd128
, ripemd160
, tiger128
, tiger160
, tiger192
, crc32
, crc32b
@IsMimeType() 检查字符串是否与有效的MIME 类型
格式匹配
@IsSemVer() 检查字符串是否为语义版本规范 (SemVer)。
@IsISSN(options?: IsISSNOptions) 检查字符串是否为 ISSN。
@IsISRC() 检查字符串是否为ISRC
@IsRFC3339() 检查字符串是否是有效的RFC 3339
日期。
数组验证装饰器
@ArrayContains(values: any[]) 检查数组是否包含给定值数组中的所有值。
@ArrayNotContains(values: any[]) 检查数组是否不包含任何给定值。
@ArrayNotEmpty() 检查给定数组是否不为空。
@ArrayMinSize(min: number) 检查数组的长度是否大于或等于指定的数字。
@ArrayMaxSize(max: number) 检查数组的长度是否小于或等于指定的数字。
@ArrayUnique(identifier?: (o) => any) 检查所有数组的值是否唯一。对象的比较是基于引用的。可选函数可以指定哪个返回值将用于比较。
对象验证装饰器
@IsInstance(value: any) 检查属性是否是传递值的实例。
其他装饰器
@Allow() 当没有为其指定其他约束时,防止剥离该属性。

1️⃣ 平面对象转化为类对象【 库 】

平面对象转化为类对象

  1. 使用 `class-transformer` 库(  `class-transformer`  依赖于 `reflect-metadata` 库 )
  1. `npm install class-transformer --save`
// 模拟 json 数据 - 有一个 users.json 在加载的用户列表:
[
  {
    "id": 1,
    "firstName": "Johny",
    "lastName": "Cage",
    "age": 27
  },
  {
    "id": 2,
    "firstName": "Ismoil",
    "lastName": "Somoni",
    "age": 50
  },
  {
    "id": 3,
    "firstName": "Luke",
    "lastName": "Dacascos",
    "age": 12
  }
]

// 定义类 - 有一个 User 类:
class User {
  id: number;
  firstName: string;
  lastName: string;
  age: number;

  getName() {
    return this.firstName + ' ' + this.lastName;
  }
  isAdult() {
    return this.age > 36 && this.age < 60;
  }
}

// 您假设您正在 User 从 users.json 文件下载类型的用户,并且可能想要编写以下代码:
// 请求到 json 以后,通过 const realUsers = plainToClass(User, users); 将 json 对象转化为类对象
fetch('users.json').then((users: Object[]) => {
  // 你可以在这里使用 users,并且类型提示也对你可用,
  // 但 users 实际上不是 User 类的实例
  // 这意味着你不能使用 User 类的方法

  // 现在 realUsers 中的每个用户都是 User 类的一个实例
  const realUsers = plainToClass(User, users);

  // 就可以直接使用函数
     realUsers.getName() 
});

通过这个库就可以约束和转化 json 对象中的返回类型,只需要修改 User 类就可以

  1. 使用装饰器@Type进行运行时的类型约束
  1. 通过 `**@type(()=>Number)**` 传入一个函数来约束类型,进行运行时的类型约束
class User {
  @type(() => Number)    
  id: number;
    @type(() => String)
  firstName: string;
    @type(() => String)
  lastName: string;
    @type(() => Number)
  age: number;

  getName() {
    return this.firstName + ' ' + this.lastName;
  }
  isAdult() {
    return this.age > 36 && this.age < 60;
  }
}

1️⃣ 装饰器扩展

2️⃣ 参数装饰器

最大作用是做依赖注入和依赖倒置
要求函数有三个参数

  1. 如果方法时静态的,则为类的本身。如果方式是实例方法,则为类的原型。
  1. 方法名称
  1. 在参数列表中的索引

image.png

2️⃣ 关于 TS 自动注入的元数据

如果安装了 **reflect-metadata**,并且导入了该库,并且在某个成员上添加了元数据,并且启用了 **"emitDecoratorMetadata": true**
则 TS 在编译结果中,会将约束的类型,作为元数据加入到相应位置
这样一来,TS 类型检测( 约束 )将有机会再运行时进行。

2️⃣ AOP( 面向切面编程 )

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。