Lesson 03-TypeScript Additional Features

Decorators

Concept

Decorators are a syntactic construct used to modify the behavior of a class at definition time. They have the following characteristics:

  1. Start with the @ symbol, followed by an expression.
  2. The expression after @ must be a function (or evaluate to a function).
  3. The function receives parameters related to the decorated object.
  4. The function either returns nothing or returns a new object to replace the decorated target.
function simpleDecorator(target: any, context: any) {
  console.log('hi, this is ' + target);
  return target;
}
@simpleDecorator
class A {} // "hi, this is class A {}"
// Valid decorators
@myFunc
@myFuncFactory(arg1, arg2)
@libraryModule.prop
@someObj.method(123)
@(wrap(dict['prop']))

@frozen
class Foo {
  @configurable(false)
  @enumerable(true)
  method() {}
  @throttle(500)
  expensiveMethod() {}
}

Decorator Structure

type Decorator = (
  value: DecoratedValue, // The decorated object
  context: {
    // Context object, described by TypeScript’s native ClassMethodDecoratorContext interface
    kind: string; // Type of the decorated object (e.g., class, method, field)
    name: string | symbol; // Name of the decorated object (e.g., class name, property name)
    addInitializer?(initializer: () => void): void; // Function to add class initialization logic
    static?: boolean; // Whether the decorated object is a static class member
    private?: boolean; // Whether the decorated object is a private class member
    access: {
      // Object containing get and set methods for the value
      get?(): unknown;
      set?(value: unknown): void;
    };
  }
) => void | ReplacementValue;

Class Decorators

function Greeter(value: any, context: any) {
  if (context.kind === 'class') {
    value.prototype.greet = function () {
      console.log('Hello');
    };
  }
}
@Greeter
class User {}
let u = new User();
u.greet(); // "Hello"

Method Decorators

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  @log
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}
function log(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;
}
const person = new Person('John');
person.greet();
// "LOG: Entering method 'greet'."
// "Hello, my name is John."
// "LOG: Exiting method 'greet'."

Property Decorators

function logged(value: any, context: any) {
  const { kind, name } = context;
  if (kind === 'field') {
    return function (initialValue: any) {
      console.log(`initializing ${name} with value ${initialValue}`);
      return initialValue;
    };
  }
}
class Color {
  @logged name = 'green';
}
const color = new Color();
// "initializing name with value green"

Getter and Setter Decorators

class C {
  @lazy
  get value() {
    console.log('Calculating...');
    return 'Expensive computation result';
  }
}
function lazy(value: any, { kind, name }: any) {
  if (kind === 'getter') {
    return function (this: any) {
      const result = value.call(this);
      Object.defineProperty(this, name, {
        value: result,
        writable: false,
      });
      return result;
    };
  }
  return;
}
const inst = new C();
inst.value;
// "Calculating..."
// "Expensive computation result"
inst.value;
// "Expensive computation result"

Accessor Decorators

type ClassAutoAccessorDecorator = (
  value: {
    get: () => unknown;
    set(value: unknown): void;
  },
  context: {
    kind: 'accessor';
    name: string | symbol;
    access: { get(): unknown; set(value: unknown): void };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
  }
) => {
  get?: () => unknown;
  set?: (value: unknown) => void;
  init?: (initialValue: unknown) => unknown;
} | void;

Decorator Application Order: Method decorators and property decorators are applied first, followed by class decorators.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love