experimentalDecorators în TypeScript

Activează decoratorii vechi (experimentali) ai lui TypeScript, folosiți de Angular și NestJS, în locul celor standard.

Sintaxă
"compilerOptions": {
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}

Ce face experimentalDecorators

Decoratorii (@jurnal pus deasupra unei metode) există în TypeScript în două variante. Varianta veche, experimentală, folosită de Angular, NestJS și TypeORM, primește (tinta, nume, descriptor). Varianta standard ECMAScript (TypeScript 5.0+) primește (valoare, context). Fără opțiune, TypeScript folosește decoratorii standard. Cu experimentalDecorators, folosește varianta veche.

emitDecoratorMetadata funcționează doar cu varianta veche (altfel TS5052): adaugă în JavaScript informații despre tipuri (design:type, design:paramtypes), citite la rulare cu biblioteca reflect-metadata. Așa face NestJS injecția de dependențe. Ambele opțiuni sunt implicit false.

Pentru cod nou, fără un framework care le cere, folosește decoratorii standard și lasă opțiunea oprită.

Parametri

Parametru Descriere
experimentalDecorators: true Decoratorii vechi, cu (tinta, nume, descriptor).
emitDecoratorMetadata: true Se scriu metadate despre tipuri pentru reflect-metadata.

Exemple

Exemplul 1: Decorator standard (fără opțiune)

Decoratorul primește metoda și un obiect context. tsc îl transformă în cod care rulează în Node.js. Exemplul rulează așa cum este.

function jurnal(metoda: Function, context: ClassMethodDecoratorContext) {
  return function (this: unknown, ...args: unknown[]) {
    console.log(`apel ${String(context.name)}(${args.join(", ")})`);
    return metoda.apply(this, args);
  };
}

class Cos {
  @jurnal
  adauga(produs: string, pret: number) {
    return `${produs}: ${pret} lei`;
  }
}

console.log(new Cos().adauga("pâine", 5));

Rezultat

apel adauga(pâine, 5)
pâine: 5 lei

Exemplul 2: Decorator vechi cu experimentalDecorators

Același efect, scris în stilul vechi. Fără opțiune, tsc îl refuză: decoratorul standard primește doar două argumente.

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "experimentalDecorators": true
  }
}

// index.ts
function jurnal(_tinta: object, nume: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (this: unknown, ...args: unknown[]) {
    console.log(`apel ${nume}(${args.join(", ")})`);
    return original.apply(this, args);
  };
}

class Cos {
  @jurnal
  adauga(produs: string, pret: number) {
    return `${produs}: ${pret} lei`;
  }
}

console.log(new Cos().adauga("pâine", 5));

Rezultat (tsc 5.9)

$ npx tsc && node index.js
apel adauga(pâine, 5)
pâine: 5 lei

$ npx tsc --experimentalDecorators false --noEmit
index.ts(10,3): error TS1241: Unable to resolve signature of method decorator when called as an expression.
  The runtime will invoke the decorator with 2 arguments, but the decorator expects 3.

Exemplul 3: Metadatele de tip

Cu emitDecoratorMetadata, fiecare câmp decorat primește __metadata("design:type", ...) cu tipul lui din TypeScript.

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

// index.ts
function camp(_t: object, _n: string) {}

class Produs {
  @camp nume: string = "";
  @camp pret: number = 0;
}
console.log(new Produs());

Rezultat (tsc 5.9)

$ npx tsc && sed -n '10,22p' index.js
function camp(_t, _n) { }
class Produs {
    nume = "";
    pret = 0;
}
__decorate([
    camp,
    __metadata("design:type", String)
], Produs.prototype, "nume", void 0);
__decorate([
    camp,
    __metadata("design:type", Number)
], Produs.prototype, "pret", void 0);

Greșeli frecvente

  • Să copiezi un decorator scris pentru Angular/NestJS într-un proiect fără experimentalDecorators. Semnătura este diferită și apare TS1241.
  • Să pornești emitDecoratorMetadata fără experimentalDecorators: eroarea TS5052.

Comenzi înrudite

  • useDefineForClassFields: Câmpurile de clasă se comportă ca în standardul JavaScript (definite cu Object.defineProperty), nu ca atribuiri în constructor.
  • target: Versiunea de JavaScript generată: sintaxa mai nouă decât ținta este transformată în cod echivalent mai vechi.
  • importHelpers: Ia funcțiile ajutătoare (__awaiter, __decorate…) din pachetul tslib, în loc să le copieze în fiecare fișier.