// 클래스 데코레이터 예시
function Logger(constructor: Function) { //
console.log("Logging...");
console.log(constructor);
}
@Logger
class Person {
name = "Max";
constructor() {
console.log("Creating person object...");
}
}
const pers = new Person();
console.log(pers);
function WithTemplate(template: string, hookId: string) { // wrapper
return function (_: Function) { // 인자가 있어야됨을 알지만 실제로 사용하진 않은 언더바(_)를 사용한다.
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
}
};
}
@WithTemplate("<h1>My Person Object</h1>", "app") // 인자를 받아야 하기 때문에 () 추가
class Person {
name = "Max";
constructor() {
console.log("Creating person object...");
}
}
const pers = new Person();
▽▽▽ 결과 화면 ▽▽▽
function Logger(logString: string) {
console.log("Logger factory"); // 첫번째
return function (_: Function) {
console.log(logString); // 네번째
};
}
function WithTemplate(template: string) {
console.log("template factory"); // 두번째
return function (_: Function) {
console.log(template); // 세번째
};
}
@Logger("LOGGING")
@WithTemplate("Rendering Template")
class Person {
name = "Max";
constructor() {
console.log("Creating person object...");
}
}
const pers = new Person();
// Logger factory
// template factory
// Rendering Template
// LOGGING
// Creating person object...
// class를 반환하는 예시
function WithTemplate(template: string, hookId: string) {
return function <T extends { new (...args: any[]): { name: string } }>(
originalConstructor: T
) {
// 기존의 constructor를 유지한 채로 새로운 contructor를 반환한다.
return class extends originalConstructor {
constructor(..._: any[]) {
super(); // 원본 훼손 X
console.log("Rendering template");
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector("h1")!.textContent = this.name;
}
}
};
};
}
@WithTemplate("<h1>My Person Object</h1>", "app")
class Person {
name = "Max";
constructor() {
console.log("Creating person object...");
}
}
const pers = new Person();
▽▽▽ 결과 화면 ▽▽▽
function Log(target: any, propertyName: string | Symbol) {
console.log("Property decorator!");
console.log(target, propertyName);
}
class Product {
@Log
title: string;
private _price: number;
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error("Invalid price - should be positive!");
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWithTax(tax: number) {
return this._price * (1 + tax);
}
}
// Accessor decorator(getter/setter에 적용되는 데코레이터)
function Log(target: any, name: String, descriptor: PropertyDescriptor) {
console.log("Accessor decorator!");
console.log(target);
console.log(name);
console.log(descriptor);
}
class Product {
title: string;
private _price: number;
@Log
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error("Invalid price - should be positive!");
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWithTax(tax: number) {
return this._price * (1 + tax);
}
}
// Method decorator
function Log(target: any, name: String | Symbol, descriptor: PropertyDescriptor) {
console.log("Method decorator!");
console.log(target);
console.log(name);
console.log(descriptor);
}
class Product {
title: string;
private _price: number;
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error("Invalid price - should be positive!");
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
@Log
getPriceWithTax(tax: number) {
return this._price * (1 + tax);
}
}
function Log(target: any, name: String | Symbol, position: number) {
console.log("Parameter decorator!");
console.log(target);
console.log(name);
console.log(position);
}
class Product {
title: string;
private _price: number;
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error("Invalid price - should be positive!");
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
getPriceWithTax(@Log tax: number) {
return this._price * (1 + tax);
}
}
class Printer {
message = "This works!";
showMessage() {
console.log(this.message);
}
}
const p = new Printer();
const button = document.querySelector("button")!;
button.addEventListener("click", p.showMessage);
▽▽▽
function Autobind(_: any, _2: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const adjustedDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: false,
get() {
const boundFunction = originalMethod.bind(this); // 여기서 this는 getter를 트리거하는 모든 것을 참조한다.
return boundFunction;
},
};
return adjustedDescriptor;
}
class Printer {
message = "This works!";
@Autobind
showMessage() {
console.log(this.message);
}
}
const p = new Printer();
const button = document.querySelector("button")!;
button.addEventListener("click", p.showMessage); // This works!
interface ValidatorConfig {
[property: string]: {
[validatableProp: string]: string[]; // ["required", "positive"]
};
}
const registeredValidators: ValidatorConfig = {};
function Required(target: any, propName: string) {
registeredValidators[target.constructor.name] = {
...registeredValidators[target.constructor.name],
[propName]: [
...(registeredValidators[target.constructor.name]?.[propName] ?? []),
"required",
],
};
}
function PositiveNumber(target: any, propName: string) {
registeredValidators[target.constructor.name] = {
...registeredValidators[target.constructor.name],
[propName]: [
...(registeredValidators[target.constructor.name]?.[propName] ?? []),
"positive",
],
};
}
function validate(obj: any) {
const objValidatorConfig = registeredValidators[obj.constructor.name];
if (!objValidatorConfig) {
return true;
}
let isValid = true;
for (const prop in objValidatorConfig) {
for (const validator of objValidatorConfig[prop]) {
switch (validator) {
case "required":
isValid = isValid && !!obj[prop];
break;
case "positive":
isValid = isValid && obj[prop] > 0;
break;
}
}
}
return isValid;
}
class Course {
@Required
title: String;
@PositiveNumber
price: number;
constructor(t: string, p: number) {
this.title = t;
this.price = p;
}
}
const courseForm = document.querySelector("form")!;
courseForm.addEventListener("submit", (event) => {
event.preventDefault();
const titleEl = document.getElementById("title") as HTMLInputElement;
const priceEl = document.getElementById("price") as HTMLInputElement;
const title = titleEl.value;
const price = +priceEl.value;
const createdCourse = new Course(title, price);
if (!validate(createdCourse)) {
alert("Invalid input, please try again!");
return;
}
console.log(createdCourse);
});