방명록
- [TypeScript] 데코레이터(udemy 강의 "Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS" 섹션 8)2024년 03월 18일 23시 36분 37초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
섹션 8: 데코레이터(장식자)
📌 데코레이터 사용 준비
// tsconfig.json { // (생략) "experimentalDecorators": true, // (생략) }
📌 데코레이터
// 여느 함수 처럼 대문자로 시작하지 않아도 되지만 대부분의 라이브러리 데코레이터의 경우 대문자로 시작 function Logger(constructor: Function) { console.log("Logging..."); } @Logger class Person { name = "김일남"; constructor() { console.log("Creating person object..."); } } const pers = new Person(); console.log(pers); /* 출력 결과 Logging... Creating person object... Person {name: '김일남'} */
📌 데코레이터 팩토리
예제 1
function Logger(logString: string) { return function (constructor: Function) { console.log(logString); }; } @Logger("LOGGING - PERSON") class Person { name = "김일남"; constructor() { console.log("Creating person object..."); } } const pers = new Person(); console.log(pers);
예제 2
function WithTemplate(template: string, hookId: string) { return function (constructor: Function) { const hookEl = document.getElementById(hookId); if (hookEl) { hookEl.innerHTML = template; } }; } @WithTemplate("<h1>My Person Object</h1>", "app") class Person { name = "김일남"; constructor() { console.log("Creating person object..."); } } const pers = new Person(); console.log(pers);
예제 3
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; } }; } @WithTemplate("<h1>My Person Object</h1>", "app") class Person { name = "김일남"; constructor() { console.log("Creating person object..."); } } const pers = new Person(); console.log(pers);
📌 데코레이터 실행 순서
하향식으로 실행되지만 반환되는 함수의 실행은 상향식임을 유의하자!
function Logger(logString: string) { console.log("LOGGER FACTORY"); // 1 return function (constructor: Function) { console.log(logString); // 5 }; } function WithTemplate(template: string, hookId: string) { console.log("TEMPLATE FACTORY"); // 2 return function (constructor: any) { console.log("Rendering template"); // 3 const hookEl = document.getElementById(hookId); const p = new constructor(); if (hookEl) { hookEl.innerHTML = template; } }; } @Logger("LOGGING") @WithTemplate("<h1>My Person Object</h1>", "app") class Person { constructor() { console.log("Creating person object..."); // 4 } } /* 출력 결과 LOGGER FACTORY TEMPLATE FACTORY Rendering template Creating person object... LOGGING */
📌 프로퍼티, 접근자, 메서드, 파라미터 데코레이터
function Log(target: any, propertyName: string | Symbol) { console.log("Property decorator!"); console.log(target, propertyName); } function Log2(target: any, name: string, descriptor: PropertyDescriptor) { console.log("Accessor decorator!"); console.log(target); console.log(name); console.log(descriptor); } function Log3( target: any, name: string | Symbol, descriptor: PropertyDescriptor ) { console.log("Method decorator!"); console.log(target); console.log(name); console.log(descriptor); } function Log4(target: any, name: string | Symbol, position: number) { console.log("Parameter decorator!"); console.log(target); console.log(name); console.log(position); } class Product { @Log // 프로퍼티 데코레이터 title: string; private _price: number; @Log2 // 접근자 데코레이터 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; } @Log3 // 메서드 데코레이터 getPriceWithTax( @Log4 // 파라미터 데코레이터 tax: number ) { return this._price * (1 + tax); } }
- 데코레이터는 클래스를 정의하는 시점에 실행
- 메서드 호출, 프로퍼티 사용과 같은 런타임에 실행되는 것이 아님
- 데코레이터 사용으로 클래스가 정의될 때 배후에서 부가적인 설정 작업 진행 가능
- 메타프로그래밍 개념
📌 클래스 데코레이터에서 클래스 반환(새로운 로직 추가)
function WithTemplate(template: string, hookId: string) { console.log("TEMPLATE FACTORY"); return function <T extends { new (...args: any[]): { name: string } }>( originalConstructor: T ) { return class extends originalConstructor { constructor(...args: any[]) { super(); // 기존 로직 상속 // 데코레이터로 새로운 로직 추가 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 = "김일남"; constructor() { console.log("Creating person object..."); } } const pers = new Person(); // 인스턴스가 생성될때 브라우저 화면에 "김일남"이 렌더링된다.
📌 Autobind 데코레이터 예제
<!-- index.html의 body 태그 --> <body> <div id="app"></div> <!-- 버튼 추가 --> <button>Click me</button> </body>
// bind 처리하는 Autobind 데코레이터 function Autobind( target: any, methodName: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; const adjDescriptor: PropertyDescriptor = { configurable: true, enumerable: false, get() { const boundFn = originalMethod.bind(this); return boundFn; }, }; return adjDescriptor; } class Printer { message = "This works!"; @Autobind // 추가 showMessage() { console.log(this.message); } } const p = new Printer(); const button = document.querySelector("button")!; // "!"는 null 아님을 타입스크립트에게 알려줌 // button.addEventListener("click", (e) => p.showMessage()); // 콜백 함수 사용한 경우 // button.addEventListener("click", p.showMessage.bind(p)); // bind() 메서드 사용한 경우 button.addEventListener("click", p.showMessage); // 위에서 정의한 Autobind 데코레이터가 바인딩 처리하기 때문에 bind() 메서드 생략
📌 유효성 검사 데코레이터 예제
Angular, NestJS와 같은 Framework에서 데코레이터를 적극적으로 사용하고 있다. 아래 코드는 텍스트, 숫자를 각각 입력 받고 버튼을 클릭하면 submit되는 간단한 form에 데코레이터를 이용해 유효성 검사 기능을 적용해본 예제이다.
<!-- index.html의 body 태그 --> <body> <form> <input type="text" placeholder="Course title" id="title"> <input type="text" placeholder="Course price" id="price"> <button type="submit">Save</button> </form> </body>
interface ValidatorConfig { [property: string]: { [validatableProp: string]: string[]; }; } const registeredValidators: ValidatorConfig = {}; function Required(target: any, propName: string) { registeredValidators[target.constructor.name] = { ...registeredValidators[target.constructor.name], [propName]: ["required"], }; } function PositiveNumber(target: any, propName: string) { registeredValidators[target.constructor.name] = { ...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) { console.log(prop); 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 createCourse = new Course(title, price); if (!validate(createCourse)) { alert("Invalid input, please try again!"); return; } console.log(createCourse); });
728x90반응형'언어·프레임워크 > TypeScript' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글