跳到主要内容

Typescript的装饰器

鱼雪

在TypeScript中,装饰器(Decorators)是一种特殊类型的声明, 它可以附加到类声明,方法,访问器,属性或参数上,并且允许修改或注释这些声明 。装饰器在设计模式和元编程方面提供了一些强大的工具。

以下是一些常见的装饰器用法:

  • 类装饰器:类装饰器应用于类声明。它接受一个参数,即类的构造函数。
  • 方法装饰器:方法装饰器应用于方法声明。它接受三个参数:目标对象,方法名和方法的描述符。
  • 属性装饰器:属性装饰器应用于属性声明。它接受两个参数:目标对象和属性名。
  • 参数装饰器:参数装饰器应用于构造函数的参数。它接受三个参数:目标对象,方法名(如果是静态方法则是构造函数本身),和参数在参数列表中的索引。

装饰器可以用于很多场景,例如实现依赖注入、方法调用日志、验证等。 在使用装饰器时,确保你的TypeScript配置启用了experimentalDecoratorsemitDecoratorMetadata选项。

experimentalDecorators的作用:

  • 启用装饰器的实验性支持。装饰器目前仍然是 ECMAScript 标准的一个提案,因此在 TypeScript 中,你需要将这个选项设置为 true 才能使用装饰器。

emitDecoratorMetadata的作用:

  • 在生成装饰器元数据(Decorator Metadata)方面的支持。当启用这个选项时,TypeScript 会为装饰器生成额外的元数据,以便在运行时进行反射。

装饰器的定义

  • 装饰器

    • 就是一个函数
    • 可以注入到:方法属性参数对象
    • 扩展其功能
  • 装饰器要解决的问题

    • 装饰器就是解决在不修改类、属性、方法、参数的时候为其添加额外的功能。
  • 高阶组件本质上也采用了装饰器的思想

    • Nets.js装饰器可以解决依赖注入的问题
    • 有了依赖注入,能大大降低项目的耦合度,大大提升项目的扩展性
  • 依赖注入核心思想

    • 使用和创建分离

装饰器的分类

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器
  • 元数据的装饰器

装饰器的两种写法

  • 使用时不传参
  • 装饰器工厂:使用时可以传参

环境搭建

  1. 安装ts-node
  • 可以使用ts-node命令直接运行*.ts文件,不需要先编译成*.js再使用node执行
npm install -g ts-node
  1. 安装concurrently,支持合并执行,同时运行多个script命令

    npm i concurrently -s
    npm i nodemon -s
  2. 新建目录,初始化TS配置,生成tsconfig.json

    tsc --init
  3. 生成Node包管理文件pakcage.json

    npm init
  4. 配置文件修改配置支持,修改tsconfig.json

  • 解开装饰器配置
    • 支持普通装饰器
    • 支持元数据装饰器
  • 设置源码目录和编译生成JS代码目录
"experimentalDecorators": true, // 支持普通装饰器
"emitDecoratorMetadata": true, // 支持元数据装饰器
"rootDir": "./src", // 源代码根目录
"outDir": "./dist", // 生成JS代码的根目录
  1. 配置package.json
  • 监控dist/teaching目录中的js文件,变化时执行node命令
  • 合并启动
  • 命令解决typescript编译装饰器类时出现的bug
    • tsconfig.json中配置装饰器配置这是避免代码报错
    • 编译执行需要在指定支持装饰器选项
  • ts-node命令配置
  • 这个配置需要根据文件名进行修改,后面会有提示
"dev:build": "tsc -w",
"dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
"start": "concurrently npm:dev:*",
"tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
"ctrl": "ts-node src/controller/HomeController.ts",
"beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
  1. 创建src/decorators

类装饰器

  • 不带参数的类装饰器

    • 代码

      function FirstClassDecorator(targetClass: any) {
      let targetClassObj = new targetClass()
      targetClassObj.buy() // 下单购买
      console.log("targetClass.name: ", targetClass.name) // targetClass.name: CustomerService
      }

      @FirstClassDecorator
      class CustomerService {
      name: string = "下单"

      constructor() {}

      buy() {
      console.log(this.name + "购买")
      }

      placeOrder() {
      console.log(this.name + "下单购买")
      }
      }
      export {}
    • package.json配置

      • 当前代码的文件名1ClassDecorator.ts
      • 修改1ClassDecorator.js1ClassDecorator.js
      {
      "name": "ts-examples",
      "version": "1.0.0",
      "description": "",
      "scripts": {
      "dev:build": "tsc -w",
      "dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
      "start": "concurrently npm:dev:*",
      "tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
      "ctrl": "ts-node src/controller/HomeController.ts",
      "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
      "concurrently": "^8.2.2",
      "nodemon": "^3.0.1"
      }
      }
    • 运行代码

      npm run start
    • JS源码

      "use strict";
      // 1. 底层JS **组合装饰器和目标类** __decorate函数
      var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
      // argsnum 参数个数
      var argsnum = arguments.length;
      // targetinfo 被装饰器修饰的目标【本案例为类】
      // argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
      // argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
      // argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
      var targetinfo = argsnum < 3 ? target :
      desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
      console.log("target: ", target) // target : [Function: CustomerService]
      console.log("targetinfo: ", targetinfo) // targetinfo: [Function: CustomerService]
      // 由于无参数,argsnum < 3, 其实是torgetinfo = target

      // decorators: 保存``装饰器数组``元素
      // decorators = [FirstClassDecorator]
      var decorator;
      // 元数据信息,支持reflect-metadata元数据
      if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
      targetinfo = Reflect.decorate(decorators, target, key, desc);
      } else
      // 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
      for (var i = decorators.length - 1; i >= 0; i--) {
      if (decorator = decorators[i]) {
      // 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
      // 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
      // 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
      // targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo

      // 这里返回一个新的targetinfo
      // 是把原来的targetinfo 传入到每一个装饰器函数中
      // 将CustomerService 传入到装饰器函数中,将执行逻辑交给装饰器内部执行
      // 如果函数有返回值
      // - 那么targetinfo就为装饰器的返回值
      // - 如果装饰器函数无返回值,则执行`|| targetinfo`
      targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
      decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
      console.log("targetinforesult:", targetinfo)
      }
      }
      // 参数数目小于三个argsnum < 3, 直接返回targetinfo
      return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
      }
      // 底层JS 组合装饰器和目标类 __decorate函数结束

      // 不带参数的装饰器
      function FirstClassDecorator(targetClass) {
      var targetClassObj = new targetClass();
      targetClassObj.buy(); // 下单购买
      console.log("targetClass.name: ", targetClass.name); // targetClass.name: CustomerService
      }

      // 被装饰的类
      var CustomerService = /** @class */ (function () {
      function CustomerService() {
      this.name = "下单";
      }
      CustomerService.prototype.buy = function () {
      console.log(this.name + "购买");
      };
      CustomerService.prototype.placeOrder = function () {
      console.log(this.name + "下单购买");
      };
      // 调用`__decorate`函数
      // - 参数
      // - 装饰器列表
      // - 类构造函数名称 CustomerService
      CustomerService = __decorate([
      FirstClassDecorator,
      __metadata("design:paramtypes", [])
      ], CustomerService);
      return CustomerService;
      }())
  • 带参数的类装饰器

    • 代码
      function FirstClassDecorator(params: any) {
      console.log("params out: ", params)
      return function(targetClass: any) {
      let targetClassObj = new targetClass()
      targetClassObj.buy()
      console.log("targetClass.name: ", targetClass.name)
      console.log("params in: ", params)
      }
      }

      @FirstClassDecorator("我是用来修饰CustomService类的装饰器参数")
      class CustomerService {
      name: string = "下单"

      constructor() {}

      buy() {
      console.log(this.name + "购买")
      }

      placeOrder() {
      console.log(this.name + "下单购买")
      }
      }

      export {}
    • package.json配置
      • 当前代码的文件名2ClassDecorator.ts
      • 修改1ClassDecorator.js2ClassDecorator.js
      {
      "name": "ts-examples",
      "version": "1.0.0",
      "description": "",
      "scripts": {
      "dev:build": "tsc -w",
      "dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/2ClassDecorator.js",
      "start": "concurrently npm:dev:*",
      "tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
      "ctrl": "ts-node src/controller/HomeController.ts",
      "beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
      "concurrently": "^8.2.2",
      "nodemon": "^3.0.1"
      }
      }
    • 运行代码
      npm run start
    • JS源码
      "use strict";
      // 1. 底层JS **组合装饰器和目标类** __decorate函数
      var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
      // argsnum 参数个数
      var argsnum = arguments.length;
      // targetinfo 被装饰器修饰的目标【本案例为类】
      // argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
      // argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
      // argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
      var targetinfo = argsnum < 3 ? target :
      desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
      console.log("target : ", target) // [Function: CustomerService]
      console.log("targetinfo: ", targetinfo) // [Function: CustomerService] // argsnum < 3 targetinfo = target

      // decorators: 保存``装饰器的数组``元素
      // decorators = [FirstClassDecorator]
      var decorator;
      // 元数据信息,支持reflect-metadata元数据
      if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
      targetinfo = Reflect.decorate(decorators, target, key, desc);
      } else
      // 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
      for (var i = decorators.length - 1; i >= 0; i--) {
      if (decorator = decorators[i]) {
      // 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
      // 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
      // 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
      // targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
      targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
      decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
      console.log("Final targetinfo result:", targetinfo)
      }
      }
      return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
      }

      // 带参数的装饰器
      function FirstClassDecorator(params: any) {
      console.log("params out: ", params)
      return function(targetClass: any) {
      let targetClassObj = new targetClass()
      targetClassObj.buy()
      console.log("targetClass.name: ", targetClass.name)
      console.log("params in: ", params)
      }
      }

      // 被装饰的类
      var CustomerService = /** @class */ (function () {
      function CustomerService() {
      this.name = "下单";
      }
      CustomerService.prototype.buy = function () {
      console.log(this.name + "购买");
      };
      CustomerService.prototype.placeOrder = function () {
      console.log(this.name + "下单购买");
      };
      // 调用
      // - 参数
      // - 装饰器列表
      // - 类构造函数名称 CustomerService

      // 和不带参数的区别就是
      // - 会把带参数装饰器先执行一遍
      // - 带参数的装饰器,返回的是一个函数作为装饰器函数
      // - 然后传入被装饰的类
      CustomerService = __decorate([
      FirstClassDecorator("我是用来修饰CustomService类的装饰器参数"),
      ], CustomerService);
      return CustomerService;
      }());

泛型工厂继承装饰器

  • 毫无相干的两个类可以相互赋值

    • 条件是:属性相同
    • 必须拥有我的所有属性(可以多,不能少),才能赋值给我
    • 等号右边完全包含左边属性
    • 父类类名,可以接受子类的类名(多态)
  • 泛型工厂继承装饰器

    • 装饰器类作为子类,继承父类的属性和方法

    • 返回装饰器子类的类名

    • 完成类的功能扩展和替换

    • 代码

      // 1. 完成日志信息的装饰器
      function LoggerInfoDecorator<T extends { new(...args: any): any}>(targetClass: T) {
      class LoggerInfoDecorator extends targetClass {
      constructor(...args: any) {
      super(...args)
      console.log("Logging INFO targetClass: ", (targetClass as any).name)
      }
      }
      return LoggerInfoDecorator
      }

      // 2. 目标类
      @LoggerInfoDecorator
      class Test {
      name!: string
      age!: number

      // 1. 先执行原来的构造函数
      constructor(name: string) {
      this.name = name
      }

      eat() {
      console.log(this.name, "Eating Food")
      }
      }

      let test = new Test("Zhangsan")
      test.eat()

      export {}
    • JS源码

      //  1.  继承源码代码
      let __extends = (function (Son, Parent) {

      function getStaticExtendsWithForIn (Son, Parent) {
      for (let key in Parent) {
      if (Object.prototype.hasOwnProperty.call(Parent, key)) {
      Son[key] = Parent[key]
      }
      }
      }

      function getStaticExtendsWithObjectkeys (Son, Parent) {
      Object.keys(Parent).forEach((key) => {
      Son[key] = Parent[key]
      })
      }

      function getStaticExtendsWithProto (Son, Parent) {
      Son.__proto__ = Parent;
      }

      let MyextendStatics = function (Son, Parent) {
      let MyextendStatics = Object.setPrototypeOf || getStaticExtendsWithForIn ||
      getStaticExtendsWithObjectkeys || getStaticExtendsWithProto
      return MyextendStatics(Son, Parent)
      }

      return function (Son, Parent) {
      MyextendStatics(Son, Parent)
      function Middle () {
      this.constructor = Son;
      }
      if (Parent) {//如果不为空 如果父类存在
      Middle.prototype = Parent.prototype;
      Son.prototype = new Middle()
      } else {// 如果父类不存在
      Son.prototype = Object.create(null)
      }
      console.log("Object.create(null):", Object.create(null));
      }
      }())

      // 2. 底层JS 组合装饰器和目标类 __decorate 函数
      var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
      // argsnum 参数个数
      var argsnum = arguments.length;
      // targetinfo 被装饰器修饰的目标【类或属性或方法或方法参数,本案例为类】
      // argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
      // argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
      // argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
      var targetinfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
      // decorator保存装饰器数组元素
      var decorator;
      // 元数据信息,支持reflect-metadata元数据
      if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
      targetinfo = Reflect.decorate(decorators, target, key, desc);
      } else
      // 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
      for (var i = decorators.length - 1; i >= 0; i--) {
      if (decorator = decorators[i]) {
      // 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
      // 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
      // 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
      // targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
      targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
      decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
      console.log("targetinforesult:", targetinfo)
      }
      }
      return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
      }
      // 底层 JS 组合装饰器和目标类 __decorate 函数结束

      // 2.装饰器类
      function LoggerInfoDecorator(targetClass) {
      var LoggerInfoDecorator = /** @class */ (function (_super) {
      __extends(LoggerInfoDecorator, _super); // ****继承关系****
      function LoggerInfoDecorator() {
      var args = [];
      for (var _i = 0; _i < arguments.length; _i++) {
      args[_i] = arguments[_i];
      }
      var _this = _super.apply(this, args) || this;
      console.log("Logging INFO targetClass: ", targetClass.name);
      return _this;
      }
      return LoggerInfoDecorator;
      }(targetClass));
      return LoggerInfoDecorator;
      }
      // 2. 目标类
      var Test = /** @class */ (function () {
      // 1. 先执行原来的构造函数
      function Test(name) {
      this.name = name;
      }
      Test.prototype.eat = function () {
      console.log(this.name, "Eating Food");
      };
      Test = __decorate([
      LoggerInfoDecorator,
      ], Test);
      return Test;
      }());

      // 4.测试
      var test = new Test("ok");

方法装饰器

  • 包含以下内容

    • 方法装饰器的实现
    • 方法装饰器拦截器意义
    • 拦截器的前置、后置功能实现
  • 不带参数方法装饰器实现

    • 代码
      /* 
      @param targetClassPrototype 类名
      @param methodname : value 方法名
      @param methodDecri: PropertyDescriptor 数据属性

      PropertyDescriptor的定义
      - 用来控制当前方法的属性

      interface PropertyDescriptor {
      configurable?: boolean // 是否可配置
      enumerable?: boolean // 是否可枚举
      value?: any // 方法名
      writable?: boolean // 是否可写
      get?(): any // get属性
      set?(v: any): void // set属性
      }
      */

      function MyMethodDecorator(targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
      console.log("targetClassPropertotype: ", targetClassPrototype)
      console.log("key: ", methodname)
      console.log("Data Attr: ", methodDecri)
      methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
      }

      // 目标类
      class RoleService {
      roleName: string = "Admin"
      constructor () {}

      @MyMethodDecorator
      DistribRoles() {
      console.log("Generating Role ...")
      }
      }
  • 带参数方法装饰器实现

    • 代码
      /* 
      @param targetClassPrototype 类名
      @param methodname : value 方法名
      @param methodDecri: PropertyDescriptor 数据属性, value 表示的是方法体

      PropertyDescriptor的定义
      - 用来控制当前方法的属性

      interface PropertyDescriptor {
      configurable?: boolean // 是否可配置
      enumerable?: boolean // 是否可枚举
      value?: any // 方法名
      writable?: boolean // 是否可写
      get?(): any // get属性
      set?(v: any): void // set属性
      }
      */

      function MyMethodDecorator(params: any) {
      return function (targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
      console.log("targetClassPropertotype: ", targetClassPrototype)
      console.log("key: ", methodname)
      console.log("Data Attr: ", methodDecri)
      console.log("Params: ", params)
      methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
      }
      }

      // 目标类
      class RoleService {
      roleName: string = "Admin"
      constructor () {}

      @MyMethodDecorator("/Service")
      DistribRoles() {
      console.log("Generating Role ...")
      }
      }
  • 方法拦截器的意义

    • 改良原有的函数,可以添加前置和后置的处理代码

属性装饰器

  • 装饰器参数

    • targetClassPrototype: object
      • 类名
    • attrname: string | symbol
      • 属性名
  • 属性装饰器的应用场景

    • 在属性声明时
      • 进行拦截
      • 添加逻辑
      • 存储相关的元数据
  • 属性装饰器应用: 元数据注入

    • 可以使用属性装饰器为属性附加额外的元数据
      • 在某些框架中非常有用,比如Angular的@Input()
      • 用于定义一个类的属性为输入属性
        class MyComponent {
        @Input title: string
        }
  • 属性装饰器应用:验证

    • 通过属性装饰器来验证类的属性值
      • 可以定义一个@IsPositive装饰器,确保属性值是正数
        function IsPositive(target: any, key: string) {
        const storedValue = this[key]
        Object.defineProperty(target, key, {
        get: function() {
        return storedValue
        },
        set: function(value) {
        if (value <= 0) {
        throw new Error("Value should be a positive number")
        }
        storedValue = value
        }
        })
        }

        class MyClass {
        @IsPositive
        positiveNumber: number
        }
    • 属性装饰器应用:日志与审计
      • 每次属性值更改时记录日志
        function LogChanges(target: any, key: string) {
        let storedValue = this[key]
        Object.defineProperty(target, key, {
        get: function() {
        return storedValue
        },
        set function(value) {
        console.log(`Property ${key} changed from ${storedValue}` to ${value})
        }
        })
        }

        class MyClass {
        @LogChanges
        myProperty: string
        }
    • 属性装饰器的其他应用场景
      • 延迟加载/懒加载
        • 当访问某个属性时,如果数据还没有被加载,则从后端或其他数据源动态加载
      • 自动绑定
        • 在React中自动绑定方法到当前实例,以便在回调或事件处理程序中使用正确的this
      • 存储映射关系
        • 在ORM框架中,属性装饰器可以用来定义属性与数据库列之间的映射关系
      • 计算属性的缓存
        • 对于计算成本高的属性,可以使用装饰器来缓存它的值,只在需要时计算

参数装饰器

  • 含义

    • 应用于类构造函数或方法参数
    • 它们在运行时可以接触到所属的类、方法和参数索引等信息
  • 参数装饰器的应用场景

    • 依赖注入
      • 当类的实例被创建时,所需的依赖会自动被注入到构造函数参数中
    • 元数据标记
      • 在某些场景下,你可能想为某个参数标记元数据,之后这些元数据可以用于验证、转换或其他目的
    • 日志和审计
      • 可以用来记录或审计方法调用和参数值,特别是对于某些关键参数
    • 转换或序列化
      • 在方法调用之前,参数装饰器可以用来转换或序列化参数值
    • 参数校验
      • 参数装饰器可以与反射元数据结合使用,对方法参数进行类型校验
    • 访问控制和授权
      • 在某些场景下,可以使用参数装饰器检查用户权限,决定是否允许对某个方法进行访问或操作
    • 关联路由和请求数据
      • 在NestJS,参数装饰器可以用来提取路由参数、查询参数、请求体或请求头

访问器装饰器

  • 定义

    • 用于拦截类的访问器(getset)
  • 访问器装饰器的应用场景

    • 值的验证
      • 在为属性赋值时(通过setter)对其进行验证
        function Validate(target: any, key: string, descriptor: PropertyDescriptor) {
        const originSet = descriptor.set
        descriptor.set = function (value: any) {
        if (!value) {
        throw new Error('Invalid value')
        }
        originSet?.call(this, value)
        }
        }

        class MyClass {
        private _name: string

        @Validate
        set name(value: string) {
        this._name = value
        }
        }
      • 日志
        • 获取或设置属性值时进行日志记录
          function Log(target: any, key: string, descriptor: PropertyDescriptor) {
          const originalGet = descriptor.get
          const originalSet = descriptor.set

          if (originalGet) {
          descriptor.get = function () {
          console.log(`Getting ${key}`)
          return originalGet.call(this)
          }
          }

          if (originalSet) {
          descriptor.set = function (value: any) {
          console.log(`Setting ${key} to ${value}`)
          originalSet.call(this, value)
          }
          }
          }

          class MyClass {
          private _value: number

          @Log
          get value(): number {
          return this._value
          }

          set value(v: number) {
          this._value = v
          }
          }
      • 延迟初始化/懒加载
        • 在第一次访问属性时初始化它
      • 计算属性的缓存
        • 对于计算代价高昂的属性,可以使用访问器装饰器来缓存其值
      • 自动通知
        • 当属性值改变时,自动触发某些动作或事件
      • 数据绑定和观察者模式
        • 当属性值更改时,通知关联的UI或其他依赖项

元数据装饰器

  • 元数据

    • 为了帮助类、方法、属性实现一定的功能,而附加在其上的一些数据
    • 分类
      • 自定义元数据
      • 内置元数据(reflect-metadata自带)
  • 初步理解

    • 在定义类或类方法或对象的时候,可以设置一些元数据,我们可以获取到在类与类方法上添加元数据
    • 需要引入第三方库reflect-metadata
    • 采用@Reflect.metadata来实现
    • 元数据是指描述东西时用的数据

装饰器执行顺序

  • 执行顺序
    • 属性装饰器
    • 方法参数装饰器
    • 方法装饰器
    • 类装饰器
  • 装饰器的使用方式
    • 前面顺序的装饰器可以保存数据
    • 后面顺序的装饰器可以获取数据

装饰器总结

  • 属性装饰器(Property Decorators)

    • 应用于属性声明
      • 不可以修改属性的描述
      • 但可以使用它来存储关于属性的元数据
    • 属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数
      • 静态成员的类构造函数,或实例成员的类原型
      • 成员名称
  • 参数装饰器(Parameter Decorators)

    • 应用于类构造函数方法的参数
    • 参数装饰器在运行时可以获取关于参数的信息
    • 参数装饰器的表达式将在运行时作为函数调用,并带有以下三个参数
      • 静态成员的类构造函数,或实例成员的类原型(the prototype of the class for an instance member)
      • 成员名称(the name of member)
      • 参数函数参数列表(function parameter list)中的序号索引(index of the parameter)
  • 方法装饰器(Method Decorators)

    • 应用于方法的属性描述符
    • 可用于观察、修改或替换方法定义
  • 类装饰器(Class Decorators)

    • 应用于类的构造函数
    • 可以用来监视、修改或替换类的定义
  • 访问器装饰器(Parameter Decorators)

    • 应用于对象的访问器属性描述符
    • 不能同时使用在一个成员的getset访问器,只能应用于其中之一
  • 案例代码

    • 代码

      function ClassDecorator() {
      console.log('ClassDecorator: evaluated');
      return function (target) {
      console.log('ClassDecorator: called');
      }
      }

      function PropertyDecorator() {
      console.log('PropertyDecorator: evaluated');
      return function (target, propertyKey) {
      console.log('PropertyDecorator: called');
      }
      }

      function MethodDecorator() {
      console.log('MethodDecorator: evaluated');
      return function (target, propertyKey, descriptor) {
      console.log('MethodDecorator: called');
      }
      }

      function ParameterDecorator() {
      console.log('ParameterDecorator: evaluated');
      return function (target, propertyKey, parameterIndex) {
      console.log('ParameterDecorator: called');
      }
      }

      @ClassDecorator()
      class MyClass {

      @PropertyDecorator()
      myProperty: string;

      myMethod(@ParameterDecorator() param: string) {
      @MethodDecorator()
      }
      }
    • 执行结果

      PropertyDecorator: evaluated
      ParameterDecorator: evaluated
      MethodDecorator: evaluated
      ClassDecorator: evaluated
      ParameterDecorator: called
      MethodDecorator: called
      PropertyDecorator: called
      ClassDecorator: called