1. First of all, because decorators are still in the proposal stage in JS, the first time you want to experience decorators in TS is on the command line ortsconfig.jsonIn the openingexperimentalDecorators

Command line: TSC — target ES5 — experimentalDecorators

tsconfig.json:

{
	"compilerOptions": {
		"target": "ES5"."experimentalDecorators": true}}Copy the code
  1. A decorator is a special type of declaration that can be attached to a class, method, accessor (getter, setter), property, or parameter.

Common decorator

function decorator(target: Object) {}

@decorator
name: string;
Copy the code

Decorator factory

function decorator() {
    return (target: Object) {}}@decorator(a)name: string;
Copy the code

See the difference between the two?

Normal decorators are invoked directly as @expression when applied, and the decorator user cannot pass any arguments to the decorator. The decorator factory, on the other hand, is called as @expression() when applied, and the decorator’s definer can define any parameters, and the decorator’s user can pass a variety of parameters, increasing the function of the decorator.

3. The order in which decorators are evaluated: defined from top to bottom, executed from bottom to top.

function f() {
    console.log('f(): evaluated');
    return function (target, propertykey, descriptor) {
            console.log('f(): called'); }}function g() {
    console.log('g(): evaluted');
    return function (target, propertyKey, descriptor) {
            console.log('g(): called'); }}class C {
    @f(a)@g(a)method(){}}Copy the code

Guess what the print was?

f(): evaluated
g(): evaluated
g(): called
f(): called
Copy the code

It’s not surprising

The evaluation or execution order of the decorator

  1. Parameter a decorator to approach a decorator, accesses a decorator decorator | attribute is applied to each instance members
  2. Parameter a decorator to approach a decorator, accesses a decorator | property decorator is applied to each of the static members
  3. Parameter decorators are applied to the constructor
  4. The class decorator is applied to the class

What is the result of the following code execution?

function classDecorator(target: Object) {
    console.log('class');
}

function methodDecorator(target: Object, propertyKey: string, descriptor: any) {
    console.log('method');
}

function propertyDecorator(target: Object, propertyKey: string) {
    console.log('property');
}

function paramDecorator(target: Object, propertyKey: string, paramIndex: number) {
    console.log('param');
}

@classDecorator
class A {

    @propertyDecorator
    public name: string;

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

    @methodDecorator
    getName(@paramDecorator address: string) {
            return this.name; }}Copy the code

I’m sure a lot of people here are going to get param Method Property class, which is what I thought it was when I first saw this code, but it’s not always going to happen. I’m sure most people are confused by this, but it says that the order of execution is first the parameter decorator, then the method decorator, then the property decorator, and finally the class decorator.

Hahaha ~ take your time to explain

** detect ** from the outside to the inside. Select * from @classdecorator (@classdecorator, @propertydecorator, @methoddecorator); select * from @classdecorator (@classdecorator, @propertydecorator, @methoddecorator); select * from @classdecorator (@classdecorator, @propertydecorator, @methoddecorator); Parameter decorator (none) → methodDecorator (@methoddecorator) → propertyDecorator (@propertydecorator) → classDecorator (@classdecorator

Later, after verification, when you get the results I’m very, very sad 😫 ~, it should be ts documents to write wrong, or to understand there are differences, the decorator must be the last decorator besides classes and parameter decorator must perform earlier than method decorators, other no execution order, you first define who is executed. Hey hey 😁

To summarize:

  • Class decorators must be executed last
  • Parameter decorators must be executed before method decorators
  • Decorators are prioritized in the order they are defined

Class decorator

function ClassDecorator(target: Object) :any | void {}
Copy the code
  1. The class decorator takes a parametertargetIs the class itself (not the instance object)
  2. Can only be modifiedtargettheprototypeProperty, the other properties areread-onlyNo modifications allowed
  3. If a class decorator returns a value, it uses the supplied constructor instead of the class declaration. See the code below
function classDecorator<T extends {new(... args:any[]) : {}} > (constructor: T) {
    return class extends constructor {
            name = 'veloma';
            address = 'Qingdao, Shandong Province'; }}@classDecorator
class Greeter {
    age = 20;
    name: string;
    constructor(m: string) {
            this.name = m; }}console.log(new Greeter('timer')); // {age: 20, name: 'veloma', address: 'Qingdao'}
Copy the code

In the above code, we replace the Greeter constructor with the return value of the classDecorator, and we can see that the variable defined in our custom constructor is added directly to this, whether or not the class has such a variable defined.

Method decorator

function methodDecorator(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<Function>) {}
Copy the code
  1. Method decorator has3Parameters, which are target, propertyKey, and Descriptor
  2. Target: Look out!! Is an instance object of the class (if the method being decorated is not static). If the method being decorated is static, then target is the class itself (not an instance object).
  3. PropertyKey: The name of the method
  4. Descriptor: method descriptor (i.eObject.definePropertyThe third parameter of

Accessor decorator

Do not write this, look at the official website can, repeat the content.

Property decorator

function PropertyDecorator(target: Object, propertyKey: string | symbol) {}
Copy the code
  1. Property decorator has2Each parameter is target and propertyKey
  2. Target: Like method decorators, it is the constructor of the class if it is a static property, or the prototype object of the class if it is not
  3. PropertyKey: The name of the property

Parameter decorator

function ParamDecorator(target: Object, propretyKey: string, paramIndex: number) {}
Copy the code
  1. The parameter decorator has3The parameters are target, propertyKey, and paramIndex
  2. Target: Same as the property decorator above
  3. PropertyKey: The name of the member, in this case the method name
  4. ParamIndex: indicates the position of the parameter

Use method decorators and parameter decorators together to implement a mandatory parameter decorator

const requiredMetadataKey = Symbol("required");
const paramsMetadataKey = Symbol('params');

/ * * *@param {Function} target- Target method *@param {string|Symbol} propertyKey- Method name *@param {number} parameterIndex- Parameter position * */
function required (target: Function, propertyKey: string | symbol, parameterIndex: number) :void {
  let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);  // Push the position into the array
  // Set the metadata name of the current method to the current method name with the value of the argument position with the @required decorator
  Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

const validate = (): MethodDecorator= >
  / * * *@param {Object} target- Target method *@param {string|Symbol} propertyName- Method name *@param {TypedPropertyDescriptor} descriptor- Method descriptor * */
  (target: Object, propertyName: string | symbol, descriptor: TypedPropertyDescriptor<any>) = > {
    letmethod = descriptor.value! ; descriptor.value =function () {
      let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName); / / (1, 0]
      if (requiredParameters) {
        for (let parameterIndex of requiredParameters) {
          if (
            parameterIndex >= arguments.length ||
            arguments[parameterIndex] === undefined ||
            arguments[parameterIndex] === null ||
            !arguments[parameterIndex]
          ) {
            throw new Error(`Missing required argument.`); }}}return method.apply(this.arguments); }}class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @validate()
  greet(@required name: string.@required age: number) :string {
    return "Hello " + name + "," + this.greeting; }}const greeter = new Greeter();
greeter('veloma'); // Error: Missing required argument.
Copy the code

Age is limited to number, so if you remove @required, you’ll get an error. Yes, but can we add it? The operator

Come and remake the greet method a little

@validate()
greet(@required name: string.@requiredage? :number) :string {
    return "hello" + name + ', ' + this.greeting;
}

const greeter = new Greeter();
greeter('veloma'); // Error: Missing required argument.
Copy the code

You can see that even though we added the parameter type, right? Operator, he still gets an error.

But there is a problem here, when the error is reported, we can only see a Missing required argument. Can we indicate which parameter of which method of which class was left unfilled? Hey hey 😁, of course. But it’s too much trouble to explain. I don’t want to explain it. Let’s look at the code.

function required(paramName: string) {
  return (target: Function.propertyKey: string | symbol, parameterIndex: number) :void= > {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);  // Push the position into the array
    // Set the metadata name of the current method to the current method name with the value of the argument position with the @required decorator
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
    const params = Reflect.getMetadata(paramsMetadataKey, target, propertyKey) || {};
    params[parameterIndex] = paramName;
    Reflect.defineMetadata(paramsMetadataKey, params, target, propertyKey); }}// Execute after the parameter decorator
const validate = (): MethodDecorator= >
  / * * *@param {Object} target- Target method *@param {string|Symbol} propertyName- Method name *@param {TypedPropertyDescriptor} descriptor- Method descriptor * */
  (target: Object, propertyName: string | symbol, descriptor: TypedPropertyDescriptor<any>) = > {
    letmethod = descriptor.value! ; descriptor.value =function () {
      const className = target.constructor.name;
      let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName); / / (1, 0]
      const params = Reflect.getMetadata(paramsMetadataKey, target, propertyName);
      if (requiredParameters) {
        for (let parameterIndex of requiredParameters) {
          if (
            parameterIndex >= arguments.length ||
            arguments[parameterIndex] === undefined ||
            arguments[parameterIndex] === null ||
            !arguments[parameterIndex]
          ) {
            throw new Error(`Missing required argument -> ${params[parameterIndex]} parameter of method ${propertyName as String} in ${className}. `); }}}return method.apply(this.arguments); }}class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @validate()
  greet(@required('name') name: string.@required('age') age? :number) :string {
    return "Hello " + name + "," + this.greeting; }}const greeter = new Greeter('good evening.');
greeter.greet('veloma'); // Error: Missing required argument -> age parameter of method greet in Greeter.
Copy the code