타입스크립트 Section 8 : 데코레이터

BRANDY·2023년 3월 14일
0

퍼스트 클래스 데코레이터

데코레이터

메타 프로그래밍하는데 유용하게 사용되며 일종의 고차함수로 클래스 선언과 멤버에 대한 주석, 메타 프로그래밍 구문을 모두 추가할 수 있는 방법을 제공하는 기능이 있다. 데코레이터 함수는 인자로 전달받은 함수의 동작을 수정한다.

function Logger(constructor: Function) {
    console.log("Logging...");
    console.log(constructor);
}

@Logger
calss Person {
    name = 'Max';
    
    constructor() {
        console.log('Creating person object...');
    }
}

const pers = new Person();
console.log(pers);

메타데이터

클래스, 변수, 메서드 타입에 대한 정보를 런타임에 사용하기 위한 도구이다.

@(at) 기호

타입스크립트에게 이것이 데코레이터임을 알려주고, 타입스크립트는 클래스 실행 시 플러그인 형태로 실행되게 해준다.

데코레이터의 특징

  1. 데코레이터는 클래스 선언, 메서드(method), 접근자(accessor), 프로퍼티(property) 또는 매개변수(parameter)에 첨부할 수 있는 특수한 종류의 선언

  2. 데코레이터 함수에는 target(현재타겟), key(속성이름), descriptor(설명)가 전달 (단, 어떤 멤버를 장식했느냐에 따라 인수가 달라짐)

  3. 메소드나 클래스 인스턴스가 만들어지는 런타임에 실행. 즉, 매번 실행되지 않음.

  4. 데코레이터는 클래스 또는 클래스 내부의 생성자, 프로퍼티 , 접근자, 메서드, 그리고 매개변수에만 장식될 수 있음

데코레이터 팩토리 작업하기

데코레이터를 만드는 대신 데코레이터 팩토리를 정의할 수 있다. 보통 데코레이터가 선언에 적용되는 방식을 원하는 대로 바꾸고 싶을때 사용되며 프로그래밍에서 함수에게 사용자가 인자를 전달할 수 있는 것과 유사하게, 데코레이터 함수 또한 팩토리를 사용해 사용자로부터 인자를 전달 받도록 설정할 수 있다.

즉, 데코레이터 팩토리는 사용자로부터 전달 받은 인자를, 내부에서 반환되는 데코레이터 함수는 데코레이터로 사용되는 것이다.

일반적으로 데코레이터에서는 팩토리를 만들어 사용하고 있기 때문에 거의 디폴트처럼 사용된다고 보면 된다.

function Logger(logString: string) {     
	return function(constructor: Function) { //실제 데코레이터
            console.log("Logging");
    	    console.log(constructor);
    }
}

@Logger('LOGGING - PERSON')
calss Person {
    name = 'Max';
    
    constructor() {
        console.log('Creating person object...');
    }
}

더 유용한 데코레이터 만들기(공부필요)

팩토리 함수와 팩토리 데코레이터를 이용하여 템플릿과 함께 새 데코레이터 팩토리를 만들 수 있다.

여러 데코레이터 추가하기

하나의 멤버에 동시에 여러개의 데코레이터를 장식할 수 있다. 이를 데코레이터 합성 이라고 부르기도 하며 실행 흐름은 다음과 같다.

각 데코레이터 표현식은 위에서 아래 방향(⬇︎)으로 평가하고
실행 결과는 아래에서 위로(⬆︎) 함수를 호출

function Logger(logString: string) {
  console.log("Logger factory");  // 1. 실행
  return function (_: Function) {
    console.log(logString);  // 4. 실행
  };
}

function WithTemplate(template: string) {
  console.log("template factory");  // 2. 실행
  return function (_: Function) {
    console.log(template);  // 3. 실행
  };
}

@Logger("LOGGING")
@WithTemplate("Rendering Template")

속성 데코레이터에 대해 알아보기(공부필요)

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);
  }
}

접근자 & 매개변수 데코레이터

접근자 / Method 데코레이터

메서드 데코레이터는 메서드 선언 직전에 선언된다. 메서드 관찰, 수정 또는 대체하는데 사용할 수 있으며 가장 많이 이용되는 데코레이터가 메서드 데코레이터이다.

클래스 데코레이터는 클래스(생성자 함수)를 extends 하는 방법으로 기능을 확장할 수 있었지만, 메서드 데코레이터는 메서드의 Property Descriptor를 수정하여 메서드를 확장한다.

메서드 데코레이터 함수가 전달 받는 인자는 총 3가지로 다음과 같다.

첫 번째 argument : static 프로퍼티라면 클래스의 생성자 함수, 인스턴스 프로퍼티라면 클래스의 prototype 객체
두 번째 argument : 해당 method의 이름
세 번째 argument : 해당 method의 property descriptor

function methodDecorator(
	target: any, // static 메서드라면 클래스의 생성자 함수, 인스턴스의 메서드라면 클래스의 prototype 객체
	propertyKey: string, // 메서드 이름
	descriptor: PropertyDescriptor // 메서드의 Property Descriptor
) {
	... 
} // return 값 무시됨

Accessor Decorator

// 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

// 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);
  }
}

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

매개 변수 선언 직전에 선언되며 함수의 매개변수 왼쪽 옆에 명시해준다.
파라미터 데코레이터 함수가 전달 받는 인자는 총 3가지로 다음과 같다

첫 번째 argument : static 프로퍼티라면 클래스의 생성자 함수, 인스턴스 프로퍼티라면 클래스의 prototype 객체
두 번째 argument : 매개변수가 들어있는 method의 이름
세 번째 argument : 메서드 파라미터 목록에서의 index

function parameterDecorator(
	target: any, // static 메서드의 파라미터 데코레이터라면 클래스의 생성자 함수, 인스턴스의 메서드라면 prototype 객체
	methodName: string, // 매개변수가 들어있는 메서드의 이름
	paramIndex: number // 매개변수의 순서 인덱스
) {
    ...
} // 리턴값은 무시됨

데코레이터는 언제 실행하는가

프로퍼티, 메소드, 액세서, 매개변수 어떤 데코레이터든 클래스를 정의할 때 실행된다. 즉 JS에서 클래스 및 constructor 함수만 정의되면 인스턴스화하지 않아도 실행된다.

클래스 데코레이터에서 클래스 반환 (및 변경)

어떤 데코레이터를 사용하느냐에 따라 어떤게 반환되고 어떤 타입스크립트를 사용할 수 있는지 결정하고 새 함수, 컨스트럭터 함수를 반환하거나 새 클래스를 반환할 수 있다. 즉 클래스가 정의될 때 동작하지 않는 로직을 추가할 수 있다.

블로그 참조 : https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%EA%B0%9C%EB%85%90-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC#%EB%A9%80%ED%8B%B0_%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0

profile
프런트엔드 개발자

0개의 댓글