Ce face decoratori
Un decorator este o funcție pe care o aplici cu @nume înaintea unei clase, metode, accesor sau câmp. Decoratorul primește elementul decorat și un obiect context (cu numele, felul elementului, addInitializer etc.) și poate returna un înlocuitor. Este felul în care framework-uri ca Angular sau NestJS adaugă comportament (logare, validare, rute) fără să schimbi codul metodei.
TypeScript 5.0 implementează decoratorii standard (propunerea ECMAScript, etapa 3). Tipurile contextului sunt ClassDecoratorContext, ClassMethodDecoratorContext, ClassFieldDecoratorContext, ClassAccessorDecoratorContext etc. Cuvântul accessor pe un câmp creează automat un getter și un setter, pe care un decorator le poate înlocui.
Proiectele mai vechi folosesc decoratorii „experimentali”, cu opțiunea experimentalDecorators (și eventual emitDecoratorMetadata). Au altă semnătură (de exemplu (target, cheie, descriptor)) și permit și decoratori de parametri; tipurile lor sunt ClassDecorator, MethodDecorator și celelalte. Cele două variante nu se amestecă într-un proiect.
Exemple
Exemplul 1: Decorator de metodă care afișează apelurile
logheaza primește metoda originală și returnează o funcție nouă care afișează argumentele și rezultatul.
function logheaza<This, Args extends unknown[], R>(
original: (this: This, ...args: Args) => R,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => R>,
) {
const nume = String(context.name);
return function (this: This, ...args: Args): R {
const rezultat = original.call(this, ...args);
console.log(`${nume}(${args.join(", ")}) = ${rezultat}`);
return rezultat;
};
}
class Calculator {
@logheaza
aduna(a: number, b: number): number {
return a + b;
}
@logheaza
tva(pret: number): number {
return Math.round(pret * 21) / 100;
}
}
const c = new Calculator();
c.aduna(2, 3);
c.tva(50);
Rezultat
aduna(2, 3) = 5
tva(50) = 10.5
Exemplul 2: Decorator de clasă și addInitializer
Decoratorul de clasă înregistrează clasa într-o listă. Decoratorul @legat leagă metoda de obiect, ca să poată fi dată ca callback fără să piardă this.
const registru: string[] = [];
function inregistreaza(clasa: abstract new (...args: never[]) => unknown, context: ClassDecoratorContext) {
registru.push(String(context.name));
return clasa as never;
}
function legat(_metoda: (this: Elev) => string, context: ClassMethodDecoratorContext<Elev, (this: Elev) => string>) {
context.addInitializer(function () {
const m = this[context.name as "salut"];
this[context.name as "salut"] = m.bind(this);
});
}
@inregistreaza
class Elev {
constructor(public nume: string) {}
@legat
salut(): string {
return `Salut, sunt ${this.nume}`;
}
}
const f = new Elev("Ana").salut; // metoda luată separat de obiect
console.log(f());
console.log(registru);
Rezultat
Salut, sunt Ana
[ 'Elev' ]
Exemplul 3: Decorator pe un câmp accessor
accessor creează getter și setter; decoratorul înlocuiește setter-ul ca să limiteze nota între 1 și 10.
function intre(min: number, max: number) {
return function <T>(
tinta: ClassAccessorDecoratorTarget<T, number>,
_context: ClassAccessorDecoratorContext<T, number>,
): ClassAccessorDecoratorResult<T, number> {
return {
set(this: T, v: number) {
tinta.set.call(this, Math.min(max, Math.max(min, v)));
},
};
};
}
class Nota {
@intre(1, 10) accessor valoare = 5;
}
const n = new Nota();
n.valoare = 14;
console.log(n.valoare);
n.valoare = -2;
console.log(n.valoare);
Rezultat
10
1
Greșeli frecvente
- Copiezi un decorator scris pentru
experimentalDecorators(cutarget, propertyKey, descriptor) într-un proiect cu decoratori standard. Semnăturile diferă și tsc dă erori de tip la aplicare. - Uiți că decoratorii de parametri (
constructor(@Inject() x)) există doar în varianta experimentală. - Pui logică grea în decoratori pentru lucruri simple. Un apel de funcție obișnuit este adesea mai ușor de citit.
Comenzi înrudite
ClassDecorator: Tipul unui decorator de clasă în stilul vechi (experimentalDecorators): primește constructorul clasei.MethodDecorator: Tipurile decoratorilor vechi pentru metode, proprietăți și parametri, folosite cu experimentalDecorators.class: Clasele din JavaScript, cu tipuri pe câmpuri, constructor și metode, plus modificatori de acces și interfețe.accesori get/set: Proprietăți calculate: get rulează cod la citire, set rulează cod la scriere, dar se folosesc ca un câmp.