文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

TypeScript 装饰器实用指南!

2024-11-30 10:34

关注

一、装饰器的概念 Summer IS HERE

在 TypeScript 中,装饰器就是可以添加到类及其成员的函数。TypeScript 装饰器可以注释和修改类声明、方法、属性和访问器。Decorator类型定义如下:

type Decorator = (target: Input, context: {
  kind: string;
  name: string | symbol;
  access: {
    get?(): unknown;
    set?(value: unknown): void;
  };
  private?: boolean;
  static?: boolean;
  addInitializer?(initializer: () => void): void;
}) => Output | void;

上面的类型定义解释如下:

二、装饰器的类型 Summer IS HERE

接下来,我们就来了解一下装饰器的各种类型。

Summer:类装饰器

当将函数作为装饰器附加到类时,将收到类构造函数作为第一个参数:

type ClassDecorator = (value: Function, context: {
  kind: "class"
  name: string | undefined
  addInitializer(initializer: () => void): void
}) => Function | void

例如,假设想要使用装饰器向 Rocket 类添加两个属性:fuel 和 isEmpty()。在这种情况下,可以编写以下函数:

function WithFuel(target: typeof Rocket, context): typeof Rocket {
  if (context.kind === "class") {
    return class extends target {
      fuel: number = 50
      isEmpty(): boolean {
        return this.fuel == 0
      }
    }
  }
}

在确保装饰元素的类型确实是类之后,返回一个具有两个附加属性的新类。或者,可以使用原型对象来动态添加新方法:

function WithFuel(target: typeof Rocket, context): typeof Rocket {
  if (context.kind === "class") {
    target.prototype.fuel = 50
    target.prototype.isEmpty = (): boolean => {
      return this.fuel == 0
    }
  }
}

可以按以下方式使用 WithFuel:

@WithFuel
class Rocket {}

const rocket = new Rocket()
console.log((rocket as any).fuel)
console.log(`empty? ${(rocket as any).isEmpty()}`)

可以看到,这里将rocket转换为any类型才能访问新的属性。这是因为装饰器无法影响类型的结构。

如果原始类定义了一个稍后被装饰的属性,装饰器会覆盖原始值。例如,如果Rocket有一个具有不同值的fuel属性,WithFuel装饰器将会覆盖该值:

function WithFuel(target: typeof Rocket, context): typeof Rocket {
  if (context.kind === "class") {
    return class extends target {
      fuel: number = 50
      isEmpty(): boolean {
        return this.fuel == 0
      }
    }
  }
}
@WithFuel
class Rocket {
  fuel: number = 75
}

const rocket = new Rocket()
console.log((rocket as any).fuel)
// 50

Summer:方法装饰器

方法装饰器可以用于装饰类方法。在这种情况下,装饰器函数的类型如下:

type ClassMethodDecorator = (target: Function, context: {
  kind: "method"
  name: string | symbol
  access: { get(): unknown }
  static: boolean
  private: boolean
  addInitializer(initializer: () => void): void
}) => Function | void

如果希望在调用被装饰的方法之前或之后执行某些操作时,就可以使用方法装饰器。

例如,在开发过程中,记录对特定方法的调用或在调用之前/之后验证前置/后置条件可能非常有用。此外,我们还可以影响方法的调用方式,例如通过延迟其执行或限制在一定时间内的调用次数。

最后,可以使用方法装饰器将一个方法标记为已废弃,并记录一条消息来警告用户,并告知他们应该使用哪个方法代替:

function deprecatedMethod(target: Function, context) {
  if (context.kind === "method") {
    return function (...args: any[]) {
      console.log(`${context.name} is deprecated and will be removed in a future version.`)
      return target.apply(this, args)
    }
  }
}

在这种情况下,deprecatedMethod函数的第一个参数是要装饰的方法。确认它确实是一个方法后(context.kind === "method"),返回一个新的函数,该函数在调用实际方法之前包装被装饰的方法并记录一条警告消息。

接下来,可以按照以下方式使用装饰器:

@WithFuel
class Rocket {
  fuel: number = 75
  @deprecatedMethod
  isReadyForLaunch(): Boolean {
    return !(this as any).isEmpty()
  }
}

const rocket = new Rocket()
console.log(`Is ready for launch? ${rocket.isReadyForLaunch()}`)

在isReadyForLaunch()方法中,引用了通过WithFuel装饰器添加的isEmpty方法。注意,必须将其转换为any类型的实例,与之前一样。当调用isReadyForLaunch()方法时,会看到以下输出,显示警告消息被正确地打印出来:

isReadyForLaunch is deprecated and will be removed in a future version.
Is the ready for launch? true

Summer:属性装饰器

属性装饰器与方法装饰器的类型非常相似:

type ClassPropertyDecorator = (target: undefined, context: {
  kind: "field"
  name: string | symbol
  access: { get(): unknown, set(value: unknown): void }
  static: boolean
  private: boolean
}) => (initialValue: unknown) => unknown | void

属性装饰器的用例与方法装饰器的用法也非常相似。例如,可以跟踪对属性的访问或将其标记为已弃用:

function deprecatedProperty(_: any, context) {
  if (context.kind === "field") {
    return function (initialValue: any) {
      console.log(`${context.name} is deprecated and will be removed in a future version.`)
      return initialValue
    }
  }
}

代码与为方法定义的 deprecatedMethod 装饰器非常相似,它的用法也是如此。

Summer:访问器装饰器

与方法装饰器非常相似的是访问器装饰器,它是针对 getter 和 setter 的装饰器:

type ClassSetterDecorator = (target: Function, context: {
  kind: "setter"
  name: string | symbol
  access: { set(value: unknown): void }
  static: boolean
  private: boolean
  addInitializer(initializer: () => void): void
}) => Function | void

type ClassGetterDecorator = (value: Function, context: {
  kind: "getter"
  name: string | symbol
  access: { get(): unknown }
  static: boolean
  private: boolean
  addInitializer(initializer: () => void): void
}) => Function | void

访问器装饰器的定义与方法装饰器的定义类似。例如,可以将 deprecatedMethod 和 deprecatedProperty 修饰合并到一个已弃用的函数中,该函数也支持 getter 和 setter:

function deprecated(target, context) {
  const kind = context.kind
  const msg = `${context.name} is deprecated and will be removed in a future version.`
  if (kind === "method" || kind === "getter" || kind === "setter") {
    return function (...args: any[]) {
      console.log(msg)
      return target.apply(this, args)
    }
  } else if (kind === "field") {
    return function (initialValue: any) {
      console.log(msg)
      return initialValue
    }
  }
}

三、装饰器的用例 Summer IS HERE

上面介绍了装饰器是什么以及如何正确使用它们,下面来看看装饰器可以帮助我们解决的一些具体问题。

Summer:计算执行时间

假设想要估计运行一个函数需要多长时间,以此来衡量应用的性能。可以创建一个装饰器来计算方法的执行时间并将其打印在控制台上:

class Rocket {
  @measure
  launch() {
    console.log("3... 2... 1... 🚀");
  }
}

Rocket 类内部有一个 launch方法。要测量launch方法的执行时间,可以附加measure 装饰器:

import { performance } from "perf_hooks";

function measure(target: Function, context) {
  if (context.kind === "method") {
    return function (...args: any[]) {
      const start = performance.now()  
      const result = target.apply(this, args)
      const end = performance.now()

      console.log(`Time: ${end - start} s`)
      return result
    }
  }
}

可以看到,measure装饰器会替换原始方法,并使用新方法来计算原始方法的执行时间并将其打印到控制台。为了计算执行时间,可以使用 Node.js 标准库中的性能钩子(Performance Hooks)API。实例化一个新的Rocket对象并调用launch方法:

const rocket = new Rocket()
rocket.launch()

将得到以下结果:

3... 2... 1... 🚀
Time: 1.062355000525713 s

Summer:使用装饰器工厂函数

要将装饰器配置为在特定场景中采取不同的行为,可以使用装饰器工厂。装饰器工厂是返回装饰器的函数。这样就能够通过在工厂中传递一些参数来自定义装饰器的行为。

来看下面的例子:

function fill(value: number) {
  return function(_, context) {
    if (context.kind === "field") {
      return function (initialValue: number) {
        return value + initialValue
      }
    }
  }
}

fill 函数返回一个装饰器,根据从工厂传入的值来改变属性的值:

class Rocket {
  @fill(20)
  fuel: number = 50
}
const rocket = new Rocket()
console.log(rocket.fuel) // 70

Summer:自动错误拦截

装饰器的另一个常见用例是检查方法调用的前置条件和后置条件。例如,假设要在调用 launch() 方法之前确保 Fuel 至少为给定值:

class Rocket {
  fuel = 50

  launch() {
    console.log("3... 2... 1... 🚀")
  }
}

假设有一个 Rocket 类,它有一个 launchToMars 方法。要发射火箭,燃料(fuel)必须高于一个值,例如 75。

下面来为它创建装饰器:

function minimumFuel(fuel: number) {
  return function(target: Function, context) {
    if (context.kind === "method") {
        return function (...args: any[]) {
          if (this.fuel > fuel) {
            return target.apply(this, args)
          } else {
            console.log(`Not enough fuel. Required: ${fuel}, got ${this.fuel}`)
          }
        }
    }
  }
}

minimumFuel是一个工厂装饰器。它接受一个 fuel 参数,表示启动特定火箭所需的燃料量。为了检查燃料条件,将原始方法包裹在一个新方法中。注意,在运行时可以自由地引用 this.fuel。

现在就可以将装饰器应用到launch方法上,并设置最低燃料量:

class Rocket {
  fuel = 50

  @minimumFuel(75)
  launch() {
    console.log("3... 2... 1... 🚀")
  }
}

如果现在调用 launch 方法,它不会发射火箭,因为当前的燃料量为 50:

const rocket = new Rocket()
rocket.launch()

Not enough fuel. Required: 75, got 50

[1]装饰器提案: https://github.com/tc39/proposal-decorators。

来源:前端充电宝内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯