Frontend Development 20 min read

Understanding @Decorator Syntax Sugar in TypeScript: Principles, Usage, and Examples

This article explains TypeScript’s @Decorator syntax sugar, showing basic examples, how the compiler transforms them into the __decorate helper, the various decorator types and factories, execution order, and the benefits and pitfalls of using decorators in frameworks like Angular and Nest.js.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Understanding @Decorator Syntax Sugar in TypeScript: Principles, Usage, and Examples

This article introduces the @Decorator syntax sugar used extensively in VS Code, Angular, Nest.js, TypeORM, MobX, Theia and other TypeScript projects. It explains why decorators are a hurdle for many developers and shows how to master them.

1. Basic decorator example

A method decorator logTime records the execution time of a method. The source code is:

function logTime(target, key, descriptor) {
  const oldMethod = descriptor.value;
  const logTime = function (...arg) {
    let start = +new Date();
    try {
      return oldMethod.apply(this, arg); // call original
    } finally {
      let end = +new Date();
      console.log(`耗时: ${end - start}ms`);
    }
  };
  descriptor.value = logTime;
  return descriptor;
}

class GuanYu {
  @logTime
  attack() { console.log('挥了一次大刀'); }

  @logTime
  run() { console.log('跑了一段距离'); }
}

const guanYu = new GuanYu();
guanYu.attack(); // [LOG]: 挥了一次大刀
               // [LOG]: 耗时: 3ms
guanYu.run();   // [LOG]: 跑了一段距离
               // [LOG]: 耗时: 3ms

The code can be run directly in the TypeScript Playground to see the compiled output.

2. How decorators are compiled

TypeScript transforms decorators into a helper function __decorate . The original implementation contains many conditional branches (checking c<3 , c>3 , Reflect.decorate , etc.). By analysing the runtime conditions (no Reflect , always c>3 , desc===null ), the helper can be simplified to:

var __decorate = function (decorators, target, key, desc) {
  var c = arguments.length;
  var r = desc = Object.getOwnPropertyDescriptor(target, key), d;
  for (var i = decorators.length - 1; i >= 0; i--) {
    if (d = decorators[i]) r = d(target, key, r) || r;
  }
  Object.defineProperty(target, key, r);
  return r;
};

This simplified version still performs the three essential steps:

Backup the original property descriptor (Step 1).

Execute each decorator function, passing target , key , and the descriptor (Step 2).

Replace the original descriptor with the result (Step 3).

3. Types of decorators

Class decorator – receives the constructor and can return a new constructor.

Property decorator – receives target and property name; return value is ignored.

Method decorator – receives target , method name, and descriptor; can replace the descriptor.

Accessor decorator – similar to method decorator but works on getter/setter descriptors.

Parameter decorator – receives target , method name, and parameter index; return value is ignored.

Decorator factories allow passing parameters, e.g. a tagged logTime :

function logTime(tag) {
  return function (target, key, descriptor) {
    const oldMethod = descriptor.value;
    const wrapper = function (...arg) {
      let start = +new Date();
      try { return oldMethod.apply(this, arg); }
      finally {
        let end = +new Date();
        console.log(`【${tag}】耗时: ${end - start}ms`);
      }
    };
    descriptor.value = wrapper;
    return descriptor;
  };
}

class GuanYu {
  @logTime('攻击')
  attack() { console.log('挥了一次大刀'); }

  @logTime('奔跑')
  run() { console.log('跑了一段距离'); }
}

Compiled output uses __decorate([logTime('攻击')], GuanYu.prototype, "attack", null); etc.

4. Execution order

When multiple decorators of the same kind are applied, they are evaluated from top to bottom (initialization) but executed from bottom to top – the classic “onion” model, similar to Koa middleware.

function dec(id) { console.log('init', id); return (t, p, d) => console.log('exec', id); }
class Example { @dec(1) @dec(2) method() {} }
// init 1
// init 2
// exec 2
// exec 1

Different decorator types have a fixed precedence: instance members (parameter → method → accessor → property) are applied before static members, which are applied before the constructor, and finally the class decorator.

5. Advantages and drawbacks

Enables adding functionality without modifying original code (Open‑Closed Principle).

Separates cross‑cutting concerns (logging, timing, validation) from business logic – AOP style.

Can be combined with dependency injection, runtime type checking, etc.

Overuse can make code harder to read and debug.

The decorator proposal is still at stage‑2 and may change; some libraries (e.g., MobX 6) have moved away from decorators.

The article concludes with a brief author bio and links to related reading.

design patternscode generationTypeScriptaopJavaScriptdecorator
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.