属性装饰器

属性装饰器应用于类的属性,可以修改属性的行为。属性装饰器接收两个参数:目标对象和属性名。

基本语法

属性装饰器是一个函数,接收两个参数。

function log(target: any, propertyKey: string) {
  console.log(`装饰属性: ${propertyKey}`)
}

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

参数说明

属性装饰器的两个参数:

  • target:对于静态属性是类的构造函数,对于实例属性是原型对象
  • propertyKey:属性名
function inspect(target: any, propertyKey: string) {
  console.log("target:", target === User.prototype)
  console.log("propertyKey:", propertyKey)
}

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

只读属性

属性装饰器可以将属性设为只读。

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

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

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

属性验证

属性装饰器可以验证属性值。

function validateEmail(target: any, propertyKey: string) {
  let value: string

  Object.defineProperty(target, propertyKey, {
    get: () => value,
    set: (newValue: string) => {
      if (!newValue.includes("@")) {
        throw new Error("邮箱格式不正确")
      }
      value = newValue
    },
    configurable: true
  })
}

class User {
  @validateEmail
  email: string = "test@example.com"
}

const user = new User()
user.email = "invalid"

格式化属性

属性装饰器可以格式化属性值。

function uppercase(target: any, propertyKey: string) {
  let value: string

  Object.defineProperty(target, propertyKey, {
    get: () => value,
    set: (newValue: string) => {
      value = newValue.toUpperCase()
    },
    configurable: true
  })
}

class User {
  @uppercase
  name: string = ""

  constructor(name: string) {
    this.name = name
  }
}

const user = new User("zhang san")
console.log(user.name)

默认值装饰器

属性装饰器可以设置默认值。

function defaultValue(value: any) {
  return function (target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
      value: value,
      writable: true,
      configurable: true
    })
  }
}

class User {
  @defaultValue("未命名")
  name: string

  @defaultValue(0)
  age: number
}

const user = new User()
console.log(user.name)
console.log(user.age)

范围限制

属性装饰器可以限制属性值的范围。

function range(min: number, max: number) {
  return function (target: any, propertyKey: string) {
    let value: number

    Object.defineProperty(target, propertyKey, {
      get: () => value,
      set: (newValue: number) => {
        if (newValue < min || newValue > max) {
          throw new Error(`${propertyKey} 必须在 ${min}${max} 之间`)
        }
        value = newValue
      },
      configurable: true
    })
  }
}

class User {
  @range(0, 150)
  age: number = 25
}

const user = new User()
user.age = 30
console.log(user.age)
user.age = 200

追踪属性变化

属性装饰器可以追踪属性变化。

function track(target: any, propertyKey: string) {
  let value: any

  Object.defineProperty(target, propertyKey, {
    get: () => value,
    set: (newValue: any) => {
      console.log(`${propertyKey}${value} 变为 ${newValue}`)
      value = newValue
    },
    configurable: true
  })
}

class User {
  @track
  name: string = ""

  @track
  age: number = 0
}

const user = new User()
user.name = "张三"
user.age = 25

实际应用

属性装饰器可以用于表单验证。

interface Validator {
  validate(value: any): boolean
  message: string
}

const validators = new Map<string, Validator[]>()

function addValidator(validator: Validator) {
  return function (target: any, propertyKey: string) {
    const key = `${target.constructor.name}.${propertyKey}`
    if (!validators.has(key)) {
      validators.set(key, [])
    }
    validators.get(key)!.push(validator)
  }
}

function required(target: any, propertyKey: string) {
  addValidator({
    validate: (value) => value !== undefined && value !== null && value !== "",
    message: `${propertyKey} 是必填项`
  })(target, propertyKey)
}

function minLength(length: number) {
  return function (target: any, propertyKey: string) {
    addValidator({
      validate: (value) => value.length >= length,
      message: `${propertyKey} 长度至少为 ${length}`
    })(target, propertyKey)
  }
}

function validate(target: any): boolean {
  let isValid = true
  for (const [key, validatorList] of validators) {
    const [className, property] = key.split(".")
    if (target.constructor.name === className) {
      const value = (target as any)[property]
      for (const validator of validatorList) {
        if (!validator.validate(value)) {
          console.log(validator.message)
          isValid = false
        }
      }
    }
  }
  return isValid
}

class UserForm {
  @required
  @minLength(2)
  name: string = ""

  @required
  email: string = ""
}

const form = new UserForm()
form.name = "张"
form.email = ""
validate(form)

小结

属性装饰器应用于类的属性,接收目标对象和属性名两个参数。属性装饰器可以实现只读属性、属性验证、格式化、默认值、范围限制等功能。属性装饰器常用于表单验证、数据绑定等场景。属性装饰器不能直接访问属性值,需要使用 Object.defineProperty 来修改属性行为。