본문 바로가기
Typescript

Typescript 5.0 업데이트에서 달라진 것들 - Decorators

by F.E.D 2023. 4. 9.

모든 링크들은 원문을 가리킵니다.

 

Decorators

데코레이터는 클래스와 멤버를 재사용 가능한 방식으로 사용자 정의할 수 있게 해주는 곧 출시될 ECMAScript 기능입니다.

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
p.greet();

greet은 매우 간단하지만 훨씬 더 복잡한 것이라고 상상해 봅시다. 비동기 로직을 수행하고 재귀 함수가 있고, 부작용이 있을 수 있다고 생각 해봅시다. 그런 상황에서 디버깅을 위해서 console.log()를 사용합니다.

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log("LOG: Entering method.");

        console.log(`Hello, my name is ${this.name}.`);

        console.log("LOG: Exiting method.")
    }
}

위와 같은 부분들을 데코레이터가 대신 해줄 수 있습니다.

logMethod라는 함수를 작성할 수 있습니다.

function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);

    function replacementMethod(this: any, ...args: any[]) {
        console.log(`LOG: Entering method '${methodName}'.`)
        const result = originalMethod.call(this, ...args);
        console.log(`LOG: Exiting method '${methodName}'.`)
        return result;
    }

    return replacementMethod;
}

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
p.greet();

// Output:
//
//   LOG: Entering method.
//   Hello, my name is Ron.
//   LOG: Exiting method.

TypeScript는 메서드 데코레이터가 사용하는 컨텍스트 개체를 모델링하는 ClassMethodDecoratorContext라는 유형을 제공합니다.

 

또한, addInitializer를 사용하여 생성자에서 바인딩을 호출하는 데코레이터를 작성할 수 있습니다.

function bound(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = context.name;
    if (context.private) {
        throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`);
    }
    context.addInitializer(function () {
        this[methodName] = this[methodName].bind(this);
    });
}

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @bound
    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
const greet = p.greet;

// Works!
greet();

@bound@loggedMethod라는 2개의 데코레이터를 사용 했습니다.

실행 순서는 "역순" 입니다.

 

또한, logMethod가 데코레이터를 반환하도록 하고 메시지를 기록하는 방법을 사용자 정의할 수 있습니다.

function loggedMethod(headMessage = "LOG:") {
    return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) {
        const methodName = String(context.name);
        function replacementMethod(this: any, ...args: any[]) {
            console.log(`${headMessage} Entering method '${methodName}'.`)
            const result = originalMethod.call(this, ...args);
            console.log(`${headMessage} Exiting method '${methodName}'.`)
            return result;
        }
        return replacementMethod;
    }
}

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod("")
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
p.greet();

// Output:
//
//    Entering method 'greet'.
//   Hello, my name is Ron.
//    Exiting method 'greet'.

experimentalDecorators는 당분간 계속 존재할 것입니다.

그러나 플래그가 없으면 데코레이터는 이제 모든 새 코드에 유효한 구문이 됩니다.

 

export 키워드 앞에 데코레이터를 추가할 수 있습니다. 

기본값 뒤에도 데코레이터를 배치할 수 있습니다.

유일하게 안되는 것은 혼합 허용은 되지 않는다는 점입니다.

//  allowed
@register export default class Foo {
    // ...
}

//  also allowed
export default @register class Bar {
    // ...
}

//  error - before *and* after is not allowed
@before export @after class Bar {
    // ...
}

위의 loggingMethod 및 바인딩된 데코레이터 예제는 의도적으로 단순하며 유형에 대한 많은 세부 정보를 생략합니다.

입력 데코레이터는 상당히 복잡할 수 있습니다. 예를 들어, 위의 logMethod의 올바른 유형 버전은 다음과 같습니다.

function loggedMethod<This, Args extends any[], Return>(
    target: (this: This, ...args: Args) => Return,
    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
    const methodName = String(context.name);

    function replacementMethod(this: This, ...args: Args): Return {
        console.log(`LOG: Entering method '${methodName}'.`)
        const result = target.call(this, ...args);
        console.log(`LOG: Exiting method '${methodName}'.`)
        return result;
    }

    return replacementMethod;
}

This, Args 및 Return 유형 매개변수를 사용하여 this 유형, 매개변수 및 원래 메서드의 반환 유형을 별도로 모델링해야 했습니다.

데코레이터 함수가 정확히 얼마나 복잡하게 정의되어 있는지는 보장하려는 항목에 따라 다릅니다.

데코레이터는 작성된 것보다 더 많이 사용되므로 일반적으로 타이핑이 잘 된 버전이 바람직하지만 가독성과 상쇄되므로 적절하게 타협해서 작업 하세요.

다음 시간에는 const type parameters에 대해서 알아보도록 하겠습니다.

 

출처 : https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/

 

Announcing TypeScript 5.0 - TypeScript

Today we’re excited to announce the release of TypeScript 5.0! This release brings many new features, while aiming to make TypeScript smaller, simpler, and faster. We’ve implemented the new decorators standard, added functionality to better support ESM

devblogs.microsoft.com

 

 

'Typescript' 카테고리의 다른 글

TypeScript 시작 방법  (0) 2021.04.29

댓글