参数装饰器

参数装饰器应用于方法参数,可以记录参数信息或修改参数行为。参数装饰器接收三个参数:目标对象、方法名和参数索引。

基本语法

参数装饰器是一个函数,接收三个参数。

function log(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`装饰参数: ${propertyKey} 的第 ${parameterIndex} 个参数`)
}

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

参数说明

参数装饰器的三个参数:

  • target:对于静态成员是类的构造函数,对于实例成员是原型对象
  • propertyKey:方法名
  • parameterIndex:参数索引(从 0 开始)
function inspect(target: any, propertyKey: string, parameterIndex: number) {
  console.log("target:", target === User.prototype)
  console.log("propertyKey:", propertyKey)
  console.log("parameterIndex:", parameterIndex)
}

class User {
  greet(@inspect name: string, @inspect age: number): string {
    return `你好,${name},你 ${age} 岁了`
  }
}

记录参数信息

参数装饰器可以记录参数信息。

const requiredParams: Map<string, number[]> = new Map()

function required(target: any, propertyKey: string, parameterIndex: number) {
  const key = `${target.constructor.name}.${propertyKey}`
  if (!requiredParams.has(key)) {
    requiredParams.set(key, [])
  }
  requiredParams.get(key)!.push(parameterIndex)
}

class UserService {
  createUser(@required name: string, @required email: string, age?: number) {
    console.log(`创建用户: ${name}, ${email}, ${age}`)
  }
}

console.log(requiredParams)

参数验证

参数装饰器可以验证参数。

const paramValidators: Map<string, Map<number, (value: any) => boolean>> = new Map()

function validate(validator: (value: any) => boolean) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const key = `${target.constructor.name}.${propertyKey}`
    if (!paramValidators.has(key)) {
      paramValidators.set(key, new Map())
    }
    paramValidators.get(key)!.set(parameterIndex, validator)
  }
}

function validateParams(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value
  const key = `${target.constructor.name}.${propertyKey}`

  descriptor.value = function (...args: any[]) {
    const validators = paramValidators.get(key)
    if (validators) {
      for (const [index, validator] of validators) {
        if (!validator(args[index])) {
          throw new Error(`参数 ${index} 验证失败`)
        }
      }
    }
    return original.apply(this, args)
  }

  return descriptor
}

function isString(value: any): boolean {
  return typeof value === "string"
}

function isNumber(value: any): boolean {
  return typeof value === "number"
}

class Calculator {
  @validateParams
  add(@validate(isNumber) a: number, @validate(isNumber) b: number): number {
    return a + b
  }
}

const calc = new Calculator()
console.log(calc.add(1, 2))
console.log(calc.add("1" as any, 2))

依赖注入

参数装饰器可以用于依赖注入。

const injectMap: Map<string, Map<number, string>> = new Map()

function inject(token: string) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const key = `${target.constructor.name}.${propertyKey}`
    if (!injectMap.has(key)) {
      injectMap.set(key, new Map())
    }
    injectMap.get(key)!.set(parameterIndex, token)
  }
}

const container = new Map<string, any>()
container.set("logger", { log: (msg: string) => console.log(`[LOG] ${msg}`) })
container.set("db", { query: (sql: string) => console.log(`执行: ${sql}`) })

function resolve(target: any) {
  const injections = injectMap.get(`${target.name}.constructor`)
  if (!injections) {
    return new target()
  }

  const args: any[] = []
  for (const [index, token] of injections) {
    args[index] = container.get(token)
  }

  return new target(...args)
}

class UserService {
  constructor(@inject("logger") private logger: any, @inject("db") private db: any) {}

  getUser(id: number) {
    this.logger.log(`获取用户 ${id}`)
    this.db.query(`SELECT * FROM users WHERE id = ${id}`)
    return { id, name: "张三" }
  }
}

const userService = resolve(UserService)
userService.getUser(1)

参数默认值

参数装饰器可以设置参数默认值。

const defaultValues: Map<string, Map<number, any>> = new Map()

function defaultValue(value: any) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const key = `${target.constructor.name}.${propertyKey}`
    if (!defaultValues.has(key)) {
      defaultValues.set(key, new Map())
    }
    defaultValues.get(key)!.set(parameterIndex, value)
  }
}

function applyDefaults(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value
  const key = `${target.constructor.name}.${propertyKey}`

  descriptor.value = function (...args: any[]) {
    const defaults = defaultValues.get(key)
    if (defaults) {
      for (const [index, value] of defaults) {
        if (args[index] === undefined) {
          args[index] = value
        }
      }
    }
    return original.apply(this, args)
  }

  return descriptor
}

class Greeter {
  @applyDefaults
  greet(name: string, @defaultValue("你好") greeting: string): string {
    return `${greeting}${name}`
  }
}

const greeter = new Greeter()
console.log(greeter.greet("张三"))
console.log(greeter.greet("李四", "欢迎"))

小结

参数装饰器应用于方法参数,接收目标对象、方法名和参数索引三个参数。参数装饰器可以记录参数信息、验证参数、实现依赖注入、设置默认值等。参数装饰器通常与方法装饰器配合使用,实现参数验证和依赖注入功能。参数装饰器不能直接修改参数值,需要配合方法装饰器来实现。