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);
});
