Decorator

TIP

配置中允许 "experimentalDecorators": true

Decorator的执行时机是在定义的地方,而不是类实例化的时候

// 传入的参数constructor取决于decorator使用在哪里,这里是构造函数
function Logging(constructor: Function) {
  console.log("logging...");
  console.log(constructor);
}
// decorator会在类定义的地方执行
@Logging
class Person {
  constructor(private name: string = "Aeroxian") {}
}






 



Decorator Factory⭐

TIP

使得Decorator更加可配置化

function Logging(logString:string) {
  return function(constructor: Function){
    console.log(logString);
    console.log(constructor);
  }

}
// decorator会在类定义的地方执行
@Logging("LOGGING - Person")
class Person {
  constructor(private name: string = "Aeroxian") {}
}

 






 



Meta Programming⭐

TIP

模仿Angular的形式

function withTemplate(template: string, hookId: string) {
  return function (constructor: any) {
    const hookEl = document.getElementById(hookId);
    const p = new constructor();
    if (hookEl) {
      hookEl.innerHTML = template;
      hookEl.querySelector("h1")!.textContent = p.name;
    }
  };
}

// decorator会在类定义的地方执行
@withTemplate("<h1>Hello TypeScript</h1>", "app")
class Person {
  constructor(public name: string = "Aeroxian") {}
}


 


 






 



<div id="app"></div>

Decorator使用的位置

TIP

除了在class上使用,还可以在Property,Accessor(setter,getter),Method,Parameter

class Product {
  @LogProperty
  title: string;
  private _price: number;

  @LogAccessor
  set price(val: number) {
    if (val >= 0) {
      this._price = val;
    } else {
      throw new Error("Invalid price - should be positive!");
    }
  }

  constructor(n: string, p: number) {
    this.title = n;
    this._price = p;
  }

  @LogMethod
  getPricewithTax(@LogParameter tax: number) {
    return this._price * (1 + tax);
  }
}

 



 













 
 



function LogProperty(target: any, name: string) {
  console.log("Property Decorator");
  console.log(target);
  console.log(name);
}
 




function LogAccessor(
  target: any,
  name: string,
  descriptor: PropertyDescriptor
) {
  console.log("Accessor Decorator");
  console.log(target);
  console.log(name);
  console.log(descriptor);
}
 









function LogMethod(target: any, name: string, descriptor: PropertyDescriptor) {
  console.log("Method Decorator");
  console.log(target);
  console.log(name);
  console.log(descriptor);
}
 





function LogParameter(target: any, name: string, position: number) {
  console.log("Parameter Decorator");
  console.log(target);
  console.log(name);
  console.log(position);
}
 





Returning (and changing) a class when a Class Decorator⭐

TIP

这段代码太惊艳了

让修饰class的decorator返回一个新的class,来修改原来的class,这里修改构造函数

function withTemplate(template: string, hookId: string) {
  console.log("Step 1");

  // T extends 一个方法的type,也可以直接写成 T extends new (...args: any[]) => { name: string }
  return function <T extends { new (...args: any[]): { name: string } }>(
    originalConstructor: T
  ) {
    console.log("Step 2");
    // 返回一个新的class,该类继承原有的类,做了一些修改
    return class extends originalConstructor {
      constructor(..._: any[]) {
        super();
        console.log("Step 3");
        const hookEl = document.getElementById(hookId);
        const p = new originalConstructor();
        if (hookEl) {
          hookEl.innerHTML = template;
          hookEl.querySelector("h1")!.textContent = this.name;
        }
      }
    };
  };
}

// decorator会在类定义的地方执行
@withTemplate("<h1>Hello TypeScript</h1>", "app")
class Person {
  constructor(public name: string = "Aeroxian") {}
}

// step 3只会在实例化的时候才执行
const person = new Person();



 
 




 
 






 












 
 

Returing a PropertyDescriptor when a method decorator⭐

TIP

让修饰方法的decorator返回一个PropertyDescriptor,来修改原来的方法

自动绑定this的例子

function AutoBind(
  target: any,
  name: string,
  propertyDescriptor: PropertyDescriptor
): PropertyDescriptor {
  console.log(propertyDescriptor);

  const originalMethod = propertyDescriptor.value;
  // 修改方法的描述
  const adjDescriptor: PropertyDescriptor = {
    // get是PropertyDescriptor接口的规范
    get() {
      console.log("xxx");
      console.log(this); // 这个例子就是Printer,因为方法绑定了@AutoBind
      // 给原来的方法绑定了this
      const boundFn = originalMethod.bind(this); // this是只被调的实例,当然是被修饰的方法
      return boundFn;
    },
  };
  return adjDescriptor;
}

class Printer {
  msg = "This works!";

  @AutoBind
  showMsg() {
    console.log(this.msg);
  }
}

const button = document.getElementById("app")!;
const p = new Printer();
// bind 让this指向p
//button.addEventListener('click',p.showMsg.bind(p));

// autobind
button.addEventListener("click", p.showMsg);







 
 
 





 
 


 





 







 
 
 
 
 

Decorator use as Validator ⭐

校验属性是否符合条件

点击查看代码
//=======================================================
interface ValidConfig {
  [properties: string]: {
    [properties: string]: string[];
  };
}
const registeredValidators: ValidConfig = {};

function Require(target: any, name: string) {
  console.log("require");
  registeredValidators[target.constructor.name] = {
    ...registeredValidators[target.constructor.name],
    [name]: ["require"],
  };
}

function PositiveNum(target: any, name: string) {
  console.log("PositiveNum");

  registeredValidators[target.constructor.name] = {
    ...registeredValidators[target.constructor.name],
    [name]: ["positiveNum"],
  };
}

function valid(obj: any) {
  const objValidatorConfig = registeredValidators[obj.constructor.name];
  if (!objValidatorConfig) {
    return;
  }
  let isValid = true;
  console.log(objValidatorConfig);

  for (const prop in objValidatorConfig) {
    for (const validator of objValidatorConfig[prop]) {
      switch (validator) {
        case "require":
          isValid = isValid && !!obj[prop];
          break;
        case "positiveNum":
          isValid = isValid && obj[prop] > 0;
          break;
      }
    }
  }
  return isValid;
}
//=======================================================

class Course {
  @Require
  title: string;
  @PositiveNum
  price: number;
  constructor(t: string, p: number) {
    this.title = t;
    this.price = p;
  }
}

const form = document.querySelector("form")!;
const formButton = form.querySelector("button")!;

formButton.addEventListener("click", (e) => {
  e.preventDefault();
  const titlEl = form.querySelector("#title")! as HTMLInputElement;
  const priceEl = form.querySelector("#price")! as HTMLInputElement;

  const title = titlEl.value;
  const price = +priceEl.value;

  const createCourse = new Course(title, price);
  console.log(createCourse);

  if (!valid(createCourse)) {
    alert("Invalid input,please try again!");
    return;
  }
  console.log(createCourse);
});