TypeScript 中的装饰器

什么是装饰器

装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数。
装饰器必须使用 @expression 方式使用,expression 必须是一个函数。

开启装饰器

// tsconfig.json:
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

简单使用demo

function sealed(target: any) {
  // do something with 'target' ...
  }

@sealed
class SealedPackage {}

装饰器工厂

function color(value: string) {
    // this is the decorator factory, it sets up
    // the returned decorator function
    return function (target: any) {
        // this is the decorator
        // do something with 'target' and 'value'...
    }
}

多个装饰器

多个装饰可在同一行定义

@f @g x

或者多行定义

@f
@g
x

调用顺序

  1. The expressions for each decorator are evaluated top-to-bottom.
  2. The results are then called as functions from bottom-to-top.
function first() {
  console.log("first(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  };
}
 
function second() {
  console.log("second(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  };
}
 
class ExampleClass {
  @first()
  @second()
  method() {}
}

//first(): factory evaluated
//second(): factory evaluated
//second(): called
//first(): called

Class装饰器

function sealed(constructor: Function) {
  Object.seal(constructor)
  Object.seal(constructor.prototype)
}

@sealed
class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }
}

防止装饰器修改构造函数

function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = "http://www...";
  };
}
 
@reportableClassDecorator
class BugReport {
  type = "report";
  title: string;
 
  constructor(t: string) {
    this.title = t;
  }
}
 
const bug = new BugReport("Needs dark mode");
console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"
 
// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;
Property 'reportingURL' does not exist on type 'BugReport'.

Method 装饰器

方法修饰符是在方法声明之前声明的。
装饰器应用于方法的属性描述符,可用于观察、修改或替换方法定义。

装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:

  1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
  2. The name of the member.
  3. The Property Descriptor for the member.

下面这个demo 是将方法设置为 enumerable = false

function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value
  }
}
class Greeter {
  greeting: string
  constructor(message: string) {
    this.greeting = message
  }

  @enumerable(false)
  greet() {
    return 'Hello, ' + this.greeting
  }
}

Accessor 装饰器

Accessor 装饰器是在访问器声明之前声明的。
Accessor 装饰器应用于访问器的属性描述符,可用于观察、修改或替换访问器的定义。

装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:

  1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
  2. The name of the member.
  3. The Property Descriptor for the member.
class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }
 
  @configurable(false)
  get x() {
    return this._x;
  }
 
  @configurable(false)
  get y() {
    return this._y;
  }
}

function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

Property 装饰器

属性装饰器是在属性声明之前声明的。

装饰器的表达式将在运行时作为函数调用,并带有以下两个参数:

  1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
  2. The name of the member.

这里的 @format(“Hello,%s”)是一个 装饰器工厂。
调用 @format(“Hello,%s”)时,它会使用 Reflect 添加 metadata entry。
调用 getFormat 时,它读取 metadata entry 的值。

import 'reflect-metadata'
const formatMetadataKey = Symbol('format')
function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString)
}
function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey)
}

class Greeter {
  @format('Hello, %s')
  greeting: string
  constructor(message: string) {
    this.greeting = message
  }
  greet() {
    let formatString = getFormat(this, 'greeting')
    return formatString.replace('%s', this.greeting)
  }
}

Parameter 装饰器

参数装饰器是在参数声明之前声明的。
参数装饰器应用于类构造函数或方法声明的函数。

装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:

  1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
  2. The name of the member.
  3. The ordinal index of the parameter in the function’s parameter list.

import 'reflect-metadata'
const requiredMetadataKey = Symbol('required')

function required(
    target: Object,
    propertyKey: string | symbol,
    parameterIndex: number
) {
    let existingRequiredParameters: number[] =
        Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || []
    existingRequiredParameters.push(parameterIndex)
    Reflect.defineMetadata(
        requiredMetadataKey,
        existingRequiredParameters,
        target,
        propertyKey
    )
}

function validate(
    target: any,
    propertyName: string,
    descriptor: TypedPropertyDescriptor<Function>
) {
    let method = descriptor.value!

    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(
            requiredMetadataKey,
            target,
            propertyName
        )
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (
                    parameterIndex >= arguments.length ||
                    arguments[parameterIndex] === undefined
                ) {
                    throw new Error('Missing required argument.')
                }
            }
        }
        return method.apply(this, arguments)
    }
}
class BugReport {
    type = 'report'
    title: string

    constructor(t: string) {
        this.title = t
    }

    @validate
    print(@required verbose: boolean) {
        if (verbose) {
            return `type: ${this.type}\ntitle: ${this.title}`
        } else {
            return this.title
        }
    }
}

装饰器官网文档

posted @ 2022-02-28 18:12  远方的少年🐬  阅读(147)  评论(0编辑  收藏  举报