farmeter / random

0 stars 0 forks source link

Typescript의 Decorator(aka. annotation) #19

Open farmeter opened 4 years ago

farmeter commented 4 years ago

개요

활성화를 위해선 Command Line 또는 config 수정

Command Line

tsc --target ES5 --experimentalDecorators

tsconfig.json

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

데코레이터 (Decorators)

function sealed(target) {
    // 'target' 변수와 함께 무언가를 수행합니다.
}

이제 @sealed를 사용할 수 있습니다.

데코레이터 팩토리 (Decorator Factories)

function color(value: string) { // 데코레이터 팩토리
    return function (target) { // 데코레이터
        // 'target'과 'value' 변수를 가지고 무언가를 수행합니다.
    }
}

데코레이터 합성 (Decorator Composition)

여러 데코레이터를 적용할 수 있습니다.

@f @g x
@f
@g
x

TypeScript에서 단일 선언에서 여러 데코레이터를 사용할 때 다음 단계가 수행됩니다.

  1. 각 데코레이터의 표현은 위에서 아래로 평가됩니다.
  2. 그런 다음 결과는 아래에서 위로 함수로 호출됩니다
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}
f(): evaluated
g(): evaluated
g(): called
f(): called

데코레이터 적용 (Decorator Evaluation)

1. 클래스 데코레이터 (Class Decorators)

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype); //밀봉하여 속성 변경 불가
}
@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

const g = new Greeter("typescript");
delete g.constructor.prototype.greet;
delete g.__proto__.greet; 
//Uncaught TypeError: Cannot delete property 'greet' of #<Greeter>

생성자를 재정의 할수도 있습니다.

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    //생성자를 오버라이드하여 새로운 클래스를 반환
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));
//{property: "property", hello: "override", newProperty: "new property"}

2. 메서드 데코레이터 (Method Decorators)

메서드 데코레이터의 표현식은 런타임에 다음 세 개의 인수와 함께 함수로 호출됩니다

  1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입입니다.
  2. 멤버의 이름
  3. 멤버의 프로퍼티 설명자

메서드 데코레이터가 값을 반환하면, 메서드의 프로퍼티 설명자 로 사용됩니다.

function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}

3. 접근자 데코레이터 (Accessor Decorators)

function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}
class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

const p = new Point(10, 20);
console.log(p.x); //10
console.log(p.y); //20

4. 프로퍼티 데코레이터 (Property Decorators)

프로퍼티 데코레이터의 표현 식은 런타임에 다음 두 개의 인수와 함께 함수로 호출됩니다

  1. 정적 멤버에 대한 클래스의 생성자 함수 또는 인스턴스 멤버에 대한 클래스의 프로토타입
  2. 멤버의 이름
import "reflect-metadata";
// 메타데이터 사용을 위한 reflect-metadata 라이브러리가 필요
// emitDecoratorMetadata 컴파일러 옵션 설정필요

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
    @format("Hello, %s")
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        let formatString = getFormat(this, "greeting");
        return formatString.replace("%s", this.greeting);
    }
}

5. 매개변수 데코레이터 (Parameter Decorators)

import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet(@required name: string) {
        return "Hello " + name + ", " + this.greeting;
    }
}

요약

  1. Java Annotation처럼 사용이 가능.
  2. Decorator는 runtime에서만 역할. (compile 시점에 무언가를 할 수 없다)
  3. 제안 단계에 있는 실험적 기능.
  4. @readonly, @override 등 이미 사용가능한 여러 lib 있음. core-decorators.js

기타

farmeter commented 4 years ago

https://www.typescriptlang.org/docs/handbook/decorators.html