装饰器

装饰器是一种特殊类型的声明,可以附加到类声明、方法、属性或参数上,以修改类的行为。装饰器使用 @expression 语法,其中 expression 是一个函数,在运行时被调用。

什么是装饰器

装饰器是一个函数,接收目标对象作为参数,返回修改后的对象或新对象。装饰器可以在不修改原有代码的情况下,扩展类的功能。

function logged(target: any) {
  console.log(`${target.name} 已被装饰`)
}

@logged
class User {
  constructor(public name: string) {}
}

装饰器的类型

TypeScript 支持以下类型的装饰器:

  • 类装饰器:应用于类构造函数
  • 方法装饰器:应用于方法
  • 属性装饰器:应用于属性
  • 参数装饰器:应用于方法参数
  • 访问器装饰器:应用于 getter/setter

启用装饰器

使用装饰器需要在 tsconfig.json 中启用。

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

experimentalDecorators 启用装饰器,emitDecoratorMetadata 启用装饰器元数据。

类装饰器

类装饰器应用于类构造函数,可以修改或替换类定义。

function sealed(target: any) {
  Object.seal(target)
  Object.seal(target.prototype)
}

@sealed
class User {
  constructor(public name: string) {}
}

类装饰器可以返回一个新的类。

function extend<T extends { new (...args: any[]): {} }>(target: T) {
  return class extends target {
    createdAt = new Date()
  }
}

@extend
class User {
  constructor(public name: string) {}
}

const user = new (User as any)("张三")
console.log(user.createdAt)

方法装饰器

方法装饰器应用于方法,可以修改方法的行为。

function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value

  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${propertyKey},参数: ${args}`)
    return original.apply(this, args)
  }

  return descriptor
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b
  }
}

const calc = new Calculator()
calc.add(1, 2)

属性装饰器

属性装饰器应用于属性,可以修改属性的行为。

function readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false
  })
}

class User {
  @readonly
  name: string = "张三"
}

const user = new User()
user.name = "李四"

参数装饰器

参数装饰器应用于方法参数,可以记录参数信息。

function required(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`${propertyKey} 的第 ${parameterIndex} 个参数是必需的`)
}

class User {
  greet(@required name: string): string {
    return `你好,${name}`
  }
}

装饰器工厂

装饰器工厂是一个返回装饰器的函数,可以接收参数。

function color(value: string) {
  return function (target: any) {
    target.prototype.color = value
  }
}

@color("red")
class Car {}

const car = new (Car as any)()
console.log(car.color)

实际应用

装饰器常用于日志、缓存、验证等场景。

function cache(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value
  const cacheMap = new Map<string, any>()

  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args)
    if (cacheMap.has(key)) {
      console.log("从缓存返回")
      return cacheMap.get(key)
    }
    const result = original.apply(this, args)
    cacheMap.set(key, result)
    return result
  }

  return descriptor
}

class DataService {
  @cache
  async fetchData(id: number): Promise<any> {
    console.log("请求服务器")
    return { id, data: "数据" }
  }
}

async function main() {
  const service = new DataService()
  await service.fetchData(1)
  await service.fetchData(1)
}

main()

小结

装饰器是一种特殊类型的声明,可以附加到类、方法、属性或参数上。装饰器使用 @expression 语法,是一个在运行时被调用的函数。TypeScript 支持类装饰器、方法装饰器、属性装饰器、参数装饰器和访问器装饰器。装饰器工厂可以接收参数并返回装饰器。装饰器常用于日志、缓存、验证等场景。