Ce face MethodDecorator
Cu opțiunea experimentalDecorators, un decorator de metodă primește trei argumente: target (prototipul clasei, sau constructorul pentru metodele statice), numele metodei și descriptorul proprietății. Schimbând descriptor.value, înlocuiești metoda. MethodDecorator este tipul acestei funcții; aceeași formă o au decoratorii de accesori get/set.
PropertyDecorator primește doar target și numele câmpului (nu și valoarea, care nu există încă) și este folosit de obicei pentru a înregistra metadate. ParameterDecorator primește în plus poziția parametrului; pentru parametrii constructorului, numele este undefined. Decoratorii de parametri există doar în varianta experimentală, unde sunt baza injecției de dependențe din Angular și NestJS.
Fără experimentalDecorators, TypeScript folosește decoratorii standard, care au altă semnătură, deci un MethodDecorator nu poate fi aplicat cu @ (vezi ultimul exemplu). Exemplele cu @ de mai jos sunt compilate cu tsc --experimentalDecorators.
Parametri
| Parametru | Descriere |
|---|---|
target |
Prototipul clasei (pentru membri de instanță) sau constructorul (pentru membri statici și parametrii constructorului). |
propertyKey |
Numele metodei, proprietății sau metodei care are parametrul. |
descriptor |
Doar la MethodDecorator: descriptorul proprietății, cu value, writable etc. |
parameterIndex |
Doar la ParameterDecorator: poziția parametrului, de la 0. |
Ce returnează
MethodDecorator: un descriptor nou sau void. Celelalte: void.
Exemple
Exemplul 1: MethodDecorator care afișează apelurile
const logheaza: MethodDecorator = (target, cheie, descriptor) => {
const original = descriptor.value as (...args: unknown[]) => unknown;
descriptor.value = function (this: unknown, ...args: unknown[]) {
console.log(`${target.constructor.name}.${String(cheie)}(${args.join(", ")})`);
return original.apply(this, args);
} as typeof descriptor.value;
return descriptor;
};
class Catalog {
@logheaza
adaugaNota(elev: string, nota: number): string {
return `${elev}: ${nota}`;
}
}
console.log(new Catalog().adaugaNota("Ana", 10));
Rezultat (compilat cu –experimentalDecorators)
Catalog.adaugaNota(Ana, 10)
Ana: 10
Exemplul 2: PropertyDecorator și ParameterDecorator
Decoratorii de parametri rulează la definirea clasei, de la ultimul parametru spre primul, iar cei ai constructorului primesc undefined ca nume.
const campuri: string[] = [];
const obligatorii = new Map<string, number[]>();
const camp: PropertyDecorator = (target, cheie) => {
campuri.push(`${target.constructor.name}.${String(cheie)}`);
};
const obligatoriu: ParameterDecorator = (target, cheie, index) => {
const nume = cheie === undefined ? "constructor" : String(cheie);
console.log(`parametrul ${index} din ${nume} (${(target as { name?: string }).name ?? target.constructor.name}) este obligatoriu`);
obligatorii.set(nume, [...(obligatorii.get(nume) ?? []), index]);
};
class Elev {
@camp nume: string;
@camp nota: number;
constructor(@obligatoriu nume: string, nota: number) {
this.nume = nume;
this.nota = nota;
}
modifica(@obligatoriu nota: number, @obligatoriu motiv: string): void {
console.log(`nota ${this.nota} -> ${nota} (${motiv})`);
this.nota = nota;
}
}
console.log(campuri);
console.log(obligatorii);
new Elev("Ana", 9).modifica(10, "contestație");
Rezultat (compilat cu –experimentalDecorators)
parametrul 1 din modifica (Elev) este obligatoriu
parametrul 0 din modifica (Elev) este obligatoriu
parametrul 0 din constructor (Elev) este obligatoriu
[ 'Elev.nume', 'Elev.nota' ]
Map(2) { 'modifica' => [ 1, 0 ], 'constructor' => [ 0 ] }
nota 9 -> 10 (contestație)
Exemplul 3: Aplicat manual, fără @
Un MethodDecorator este o funcție obișnuită: îl poți apela tu cu prototipul, numele și descriptorul, exact cum face codul generat de tsc. Exemplul rulează fără experimentalDecorators.
const dublu: MethodDecorator = (_target, _cheie, descriptor) => {
const original = descriptor.value as unknown as (x: number) => number;
return { ...descriptor, value: ((x: number) => original(x) * 2) as typeof descriptor.value };
};
class Calcul {
patrat(x: number): number {
return x * x;
}
}
const descriptor = Object.getOwnPropertyDescriptor(Calcul.prototype, "patrat")!;
const nou = dublu(Calcul.prototype, "patrat", descriptor) || descriptor;
Object.defineProperty(Calcul.prototype, "patrat", nou);
console.log(new Calcul().patrat(3));
Rezultat
18
Exemplul 4: MethodDecorator fără experimentalDecorators
Cu decoratorii standard, metoda este apelată cu două argumente (valoare, context), nu cu trei.
const logheaza: MethodDecorator = (_target, _cheie, descriptor) => descriptor;
class Catalog {
@logheaza
adauga(): void {}
}
new Catalog().adauga();
Eroarea afișată de tsc
exemplu.mts(3,5): 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.
exemplu.mts(3,6): error TS1270: Decorator function return type 'void | TypedPropertyDescriptor<unknown>' is not assignable to type 'void | (() => void)'.
Type 'TypedPropertyDescriptor<unknown>' is not assignable to type 'void | (() => void)'.
Greșeli frecvente
- Aplici un
MethodDecoratorcu@fărăexperimentalDecoratorsîntsconfig.json. tsc dă Unable to resolve signature of method decorator. - Încerci să citești valoarea unui câmp într-un
PropertyDecorator. Decoratorul rulează la definirea clasei, înainte să existe vreun obiect.
Comenzi înrudite
ClassDecorator: Tipul unui decorator de clasă în stilul vechi (experimentalDecorators): primește constructorul clasei.decoratori: Funcții puse cu @ deasupra claselor, metodelor sau câmpurilor, care le modifică sau le înlocuiesc comportamentul.accesori get/set: Proprietăți calculate: get rulează cod la citire, set rulează cod la scriere, dar se folosesc ca un câmp.