类装饰器

类装饰器应用于类构造函数,可以修改、替换或扩展类定义。类装饰器在类声明时被调用,接收类的构造函数作为唯一参数。

基本语法

类装饰器是一个函数,接收构造函数作为参数。

function sealed(target: any) {
  console.log(`密封 ${target.name}`)
  Object.seal(target)
  Object.seal(target.prototype)
}

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

装饰器在类定义时立即执行。

修改类

类装饰器可以修改类的原型。

function addMethod(target: any) {
  target.prototype.greet = function () {
    return `你好,我是 ${this.name}`
  }
}

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

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

替换类

类装饰器可以返回一个新的类,完全替换原有类。

function extend<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    createdAt = new Date()
    id = Math.random().toString(36).substr(2, 9)
  }
}

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

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

装饰器工厂

类装饰器可以接收参数。

function setAge(age: number) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      age = age
    }
  }
}

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

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

单例模式

类装饰器可以实现单例模式。

function singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  let instance: any

  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) {
        return instance
      }
      super(...args)
      instance = this
    }
  }
}

@singleton
class Database {
  constructor(public host: string) {
    console.log("连接数据库")
  }
}

const db1 = new (Database as any)("localhost")
const db2 = new (Database as any)("localhost")
console.log(db1 === db2)

日志装饰器

类装饰器可以添加日志功能。

function logged(target: any) {
  const original = target

  const newConstructor: any = function (...args: any[]) {
    console.log(`创建 ${original.name} 实例`)
    console.log(`参数: ${JSON.stringify(args)}`)
    return new original(...args)
  }

  newConstructor.prototype = original.prototype
  return newConstructor
}

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

const user = new (User as any)("张三", 25)
console.log(user.name)

混合装饰器

类装饰器可以实现混合模式。

interface Serializable {
  serialize(): string
}

function serializable(target: any) {
  target.prototype.serialize = function () {
    return JSON.stringify(this)
  }
}

@serializable
class User {
  constructor(public name: string, public age: number) {}
}

const user = new (User as any)("张三", 25)
console.log((user as any).serialize())

实际应用

类装饰器常用于依赖注入框架。

type Constructor<T = any> = new (...args: any[]) => T

const container = new Map<string, any>()

function Injectable(key: string) {
  return function <T extends Constructor>(target: T) {
    container.set(key, new target())
    return target
  }
}

function Inject(key: string) {
  return function (target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
      get: () => container.get(key)
    })
  }
}

@Injectable("logger")
class Logger {
  log(message: string) {
    console.log(`[LOG] ${message}`)
  }
}

@Injectable("userService")
class UserService {
  @Inject("logger")
  logger!: Logger

  getUser(id: number) {
    this.logger.log(`获取用户 ${id}`)
    return { id, name: "张三" }
  }
}

const userService = container.get("userService") as UserService
userService.getUser(1)

小结

类装饰器应用于类构造函数,可以修改、替换或扩展类定义。类装饰器接收构造函数作为唯一参数,可以返回一个新的类。类装饰器可以实现单例模式、日志记录、混合模式等功能。装饰器工厂可以让类装饰器接收参数。类装饰器在依赖注入框架中广泛使用。