Decorators and Metadata Refection in TypeScript
March 21st, 2022

Annotation & Decorators

See annotation vs decorator

Decorators in TypeScript

declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

declare type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol
) => void;

declare type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

declare type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) => void;

Decorator Example: log

class C {
  @log
  double(n: number) {
    return n * 2;
  }
}

// method decorator log
function log(target: Function, key: string, desc: any) {
  return {
    value(...args: any[]) {
      const result = desc.value.apply(this, args);

      console.log(
        `Call: ${key}(${args.map(arg => JSON.stringify(arg)).join()})`
      );
      return result;
    }
  };
}

// client
const instance = new C();

c.double(5);

Try to explain below questions from the yield code:

  • Where is decorator being invoked?
  • Who is providing the decorator arguments?
  • Where is the __decorate function declared?
"use strict";
var __decorate =
  (this && this.__decorate) ||
  function(decorators, target, key, desc) {
    var c = arguments.length,
      r =
        c < 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
      r = Reflect.decorate(decorators, target, key, desc);
    else
      for (var i = decorators.length - 1; i >= 0; i--)
        if ((d = decorators[i]))
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };
// method decorator log
function log(target, key, desc) {
  return {
    value(...args) {
      const result = desc.value.apply(this, args);
      console.log(
        `Call: ${key}(${args.map(arg => JSON.stringify(arg)).join()})`
      );
      return result;
    }
  };
}
class C {
  double(n) {
    return n * 2;
  }
}
__decorate([log], C.prototype, "double", null);
// client
const instance = new C();
c.double(5);

Parameter Decorator

A parameter decorator should only be used to generate some sort of metadata. Once the metadata has been created we can use another decorator to read it.

Metadata Reflection API

interface Dto {
  test: string;
}

function logType(target: any, key: string) {
  console.log(
    `${key}: ${Reflect.getMetadata("design:type", target, key).name}`
  );
}

function logParameterType(target: any, key: string) {
  console.log(
    "logParameterType",
    Reflect.getMetadata("design:paramtypes", target, key)
  );
  console.log(
    "logParameterType",
    `${key}: ${Reflect.getMetadata("design:paramtypes", target, key).map(
      t => t.name
    )}`
  );
}

function logReturnType(target: any, key: string) {
  console.log(
    "logReturnType",
    `${key}: ${Reflect.getMetadata("design:returntype", target, key)}`
  );
}

class C {
  @logType
  public c: string;

  @logParameterType
  @logReturnType
  public operation(param1: string, param2: string, param3: Dto): string {
    return param1 + param2;
  }
}

// client
new C();

// yield
// c: String
// logReturnType operation: function String() { [native code] }
// logParameterType (3) [ƒ, ƒ, ƒ]
// logParameterType operation: String,String,Object

Refs

Subscribe to guanbinrui.eth
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from guanbinrui.eth

Skeleton

Skeleton

Skeleton