본문 바로가기
Web/JavaScript

[TypeScript] Decorators

by llHoYall 2020. 10. 24.

Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.

So you should set the feature in your tsconfig.js.

{
  "compilerOptions": {
    "target": "es5"
    "experimentalDecorators": true
    ...
  }
}

 

A decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter.

Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.

Decorator Factories

If we want to customize how a decorator is applied to a declaration, we can write a decorator factory.

A decorator factory is simply a function that returns that expression that will be called by the decorator at runtime.

function decoratorFn() {
  console.log("Hello");
  return (
    target: Object,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) => {
    console.log("Decorator");
  };
}

Decorator Composition

Multiple decorators can be applied to a declaration.

function sayHi() {
  console.log("Hi");
  return (
    target: Object,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) => {
    console.log("Who are you?");
  };
}

function sayBye() {
  console.log("Bye");
  return function (
    target: Object,
    propertyKey: string,
  ) {
    console.log("Farewell");
  };
}

class Tester {
  @sayHi()
  @sayBye()
  runTest() {
    console.log("Run test");
  }
}

const tester = new Tester();
tester.runTest();
// Hi
// Bye
// Farewell
// Who are you?
// Run test

When evaluating multiple decorators on a single declaration in TypeScript:

  1. The expressions for each decorator are evaluated top-to-bottom.
  2. The results are then called as functions from bottom-to-top.

Decorator Evaluation

There is a well-defined order to how decorators applied to various declarations inside of a class are applied:

  1. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member.
  2. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static member.
  3. Parameter Decorators are applied for the constructor.
  4. Class Decorators are applied for the class.

Class Decorators

A class decorator is declared just before a class declaration.

The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

A class decorator cannot be used in a declaration file, or in any other ambient context.

function necklace(constructor: Function) {
  console.log(constructor)
}

@necklace
class Dog {
  constructor(public name: string) { }

  bark() {
    console.log(`${this.name}: bowwow`)
  }
}

const dog = new Dog('happy');
// f Dog(name) {
//   this.name = name;
// }

dog.bark();
// happy: bowwow!

We can override the constructor like below with decorator.

function changeConstructor<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    hobby = 'study';
    age = 27;
  };
}

@changeConstructor
class Person {
  constructor(public name: string, public age: number) { }
}

const person = new Person('HoYa', 18);
console.log(person);
// class_1 {name: 'HoYa', age: 27, hobby: 'study'}

Method Decorators

A method decorator is declared just before a method declaration.

The decorator is applied to the property descriptor for the method and can be used to observe, modify, or replace a method definition.

A method decorator cannot be used in a declaration file, on an overload, or in any other ambient context.

 

The expression for the method decorator will be called as a function at runtime, with the following three arguments:

  1. Either the constructor function of the class for a static member or the prototype of the class for an instance member.
  2. The name of the member.
  3. The property descriptor for the member.
function gender(isMale: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = isMale;
    console.log(`Gender: ${isMale ? 'Male' : 'Female'}`);
  };
}

class Person {
  @gender(true)
  display() {
    console.log('Hello');
  }
}

const person = new Person();
// Gender:  Male

Accessor Decorator

An accessor decorator is declared just before an accessor declaration.

The accessor decorator is applied to the property descriptor for the accessor and can be used to observe, modify, or replace an accessor's definitions.

An accessor decorator cannot be used in a declaration file, or in any other ambient context.

 

The expression for the accessor decorator will be called as a function at runtime, with the following three arguments:

  1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
  2. The name of the member.
  3. The property descriptor for the member.
function adult(value: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    if (value >= 19) {
      console.log('Adult')
    } else {
      console.log('Child')
    }
  };
}

class Person {
  constructor(private _age: number) { }

  @adult(18)
  get age() {
    return this._age;
  }
}

const person = new Person(18);
// Child
console.log(person.age);
// 18

'Web > JavaScript' 카테고리의 다른 글

[React] Get Keyboard Input (TypeScript)  (0) 2020.12.28
[TypeScript] Type Inference  (0) 2020.10.25
[TypeScript] Iterators and Generators  (0) 2020.10.23
[TypeScript] Interface  (0) 2020.10.17
[TypeScript] Destructuring and Spread  (0) 2020.10.16

댓글