TypeScript decorator 응용

강정우·2023년 6월 21일
0

TypeScript

목록 보기
22/22
post-thumbnail

class를 class decorator로 바꾸기(decorator의 return)

function WidthTemplate(template: string, hookId: string) {
    console.log('TEMPLATE FACTORY');
    return function <T extends {new(...args:any[]):{name:string}}> (originalConstructor: T) {
        return class extends originalConstructor{
            constructor(..._: any[]) {
                super();
                console.log('Rendering template');
                const hookEl = document.getElementById(hookId);
                if (hookEl) {
                    hookEl.innerHTML = template;
                    hookEl.querySelector('h1')!.textContent = this.name;
                }
            }
        }
    };
}

@WidthTemplate('<h1> My Person Object</h1>', 'app')
class Person {
    name = 'Max';

    constructor() {
        console.log('Creating person object...');
    }
}

const pers = new Person();
  • 위와같이 decorator factory에 decorator가 있는데 이를 한 번 더 return하면 이제는 class가 정의될 때가 아닌 class를 인스턴스화를 시킬때 작동한다.

  • super() 함수로 원래 존재하는 original class를 저장하고 그리고 기존 클래스를 다른 커스텀 클래스로 바꾸어서 클래스가 인스턴스화 되었을 때 동작할 수 있도록 바꿀 수 있으며 그 와중에 어떠한 로직이 작동할 수 있도록 할 수도 있다. 여기서는 어떠한 값을 받고 이를 다른 기존의 클래스를 상속받으며 다른 클래스로 바꾸는 작업을 하였고 바뀐 다른 클래스는 화면에 h1 태그로 기존 class의 name property를 표시하도록 작성되었다.

decorator로 auto bind 메서드 만들어보기

  • 사실 바로 앞서 포스팅한 내용을 보면 accessor decorator, method decorator는 return 값이 없다고 하였는데 사실 만들면 만들 순 있다.
class Printer{
    message = 'This works!';

    showMessage() {
        console.log(this.message);
    }
}

const p = new Printer();
const button = document.querySelector('button')!;
button.addEventListener('click', p.showMessage);
  • 만약 이러한 코드가 있다고 가정하자 여기서의 문제는 this 키워드가 button.으로 시작하는 context의 this를 가르키기 때문에 의도대로 동작하지 않는다는 것이다. 그래서 우리는 앞서 주구장창 this에 값을 넘겨주기위한 메서드로 .bind를 사용해왔다.

  • 자, 그럼 이제 decorator로 해당 Printer class를 감싸며 어디서 불러도 this가 p를 가르킬 수 있도록 만들어보겠다.

function Autobind(_: any, _2: 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")!;
button.addEventListener('click', p.showMessage);
  • 여기서 getter를 사용하는 이유는 사용자가 이 속성에 액세스하려고 할 때 추가 논리를 실행할 수 있기 때문이다.
    그래서 우리는 이 get function을 직접 실행하는 것이 아니라, get function이 실행하기 전에 개입하여 추가 작업을 수행할 수 있다.

  • 그럼 위 getter 안에 들어있는 bind의 this는 무얼 나타내는가? =>
    getter 메서드는 그것이 속한 구체적인 개체에 의해 트리거되므로 getter 메서드 내부는 항상 우리가 getter를 정의한 개체를 참조한다.

  • 따라서 여기 이게 우리가 메서드를 처음 정의했던 개체를 참조하고 원본 메서드에 대해 안전하게 바인딩할 수 있고 원본 메서드 안의 이것이 완전히 동일한 개체를 참조하도록 할 수 있다.

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글