Chaque développeur Angular a vu des décorateurs dans le code de code temporel. Ils sont utilisés pour décrire les modules, configurer l'injection de dépendances ou configurer un composant. En d'autres termes, les décorateurs sont utilisés pour décrire des informations supplémentaires, ou métadonnées, pour un framework ou un compilateur (dans le cas d'Angular). De plus, Angular n'est qu'un exemple. Il existe de nombreuses autres bibliothèques qui utilisent des décorateurs pour la simplicité et la visualisation du code, comme une approche déclarative. En tant que développeur .NET dans le passé, je vois beaucoup de similitudes entre les décorateurs TS et les attributs .NET. Enfin, le cadre applicatif back-end croissant de NestJS (une abstraction sur Node) est également basé sur une utilisation intensive des décorateurs et une approche déclarative. Comment tout cela fonctionne et comment utiliser les décorateurs dans votre code,pour le rendre plus pratique et lisible? Nous comprenons tous qu'après avoir compilé du code TS, nous obtenons du code Javascript. Dans lequel il n'y a pas de concept de décorateur, comme beaucoup d'autres fonctionnalités Typescript. Par conséquent, pour moi, la question la plus intéressante est de savoir ce que le décorateur devient après la compilation. Poursuivant ce dossier, j'ai prononcé un discours lors de la réunion de Minsk et je souhaite partager l'article.

. , .
@NgModule({
imports: [
CommonModule,
],
exports: [
],
})
export class NbThemeModule {}
- for component declaration
@Component({
selector: 'nb-card-header',
template: `<ng-content></ng-content>`,
})
export class NbCardHeaderComponent {}
, , , .
, tsconfig.json
, emitDecoratorMetadata
experimentalDecorators
, .
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2017",
},
}
, — , , , get , . @expression
, @
. expression
. , , .
, — , , . - .
, — . , -, ( ). , , .
— . Typescript :
declare type MethodDecorator =
<T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>)
=> TypedPropertyDescriptor<T> | void;
, . :
:
interface TypedPropertyDescriptor<T> {
enumerable?: boolean;
configurable?: boolean;
writable?: boolean;
value?: T;
get?: () => T;
set?: (value: T) => void;
}
, , .
, - , . Javascript .
, - . — .
class TestServiceDeco {
@LogTime()
testLogging() {
...
}
}
, . Typescript , . , - , .
:
function LogTime() {
return (target: Object, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) => {
const method = descriptor.value;
descriptor.value = function(...args) {
console.time(propertyName || 'LogTime');
const result = method.apply(this, args);
console.timeEnd(propertyName || 'LogTime');
return result;
};
};
}
, — , . — target, propertyName . .
— , . , , . .
Javascript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function LogTime() {
return (target, propertyName, descriptor) => {
const method = descriptor.value;
descriptor.value = function (...args) {
console.time(propertyName || 'LogTime');
const result = method.apply(this, args);
console.timeEnd(propertyName || 'LogTime');
return result;
};
};
}
exports.LogTime = LogTime;
, Typescript . :
Object.defineProperty(exports, "__esModule", { value: true });
const log_time_decorator_1 = require("../src/samples/log-time.decorator");
class TestServiceDeco {
testLogging() {
... }
}
__decorate([
log_time_decorator_1.LogTime(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], TestServiceDeco.prototype, "testLogging", null);
__decorate
, .
, target
prototype
, .
, __decorate
, , . , . .
, . — . — . — Dependency Injection. . , :
@CustomBehavior({
singleton: false,
})
class TestServiceDeco {
constructor() {
console.log('TestServiceDeco ctor');
}
}
, Typescript:
declare type ClassDecorator =
<TFunction extends Function>(target: TFunction)
=> TFunction | void;
:
import 'reflect-metadata';
interface Metadata {
singleton?: boolean;
}
function CustomBehavior(metadata: Metadata) {
return function(ctor: Function) {
Reflect.defineMetadata('metadataKey', metadata, ctor);
}
}
. — singelton- . .
:
Reflect-metadata Typescript. , — . , , .
import 'reflect-metadata';
const instancesMap: Map<Object, Object> = new Map<Object, Object>();
function getInstance<T>(tType: new () => T): T {
let metadata = Reflect.getMetadata('metadataKey', tType) as Metadata;
if (metadata.singleton) {
if (!instancesMap.has(tType)) {
instancesMap.set(tType, new tType());
}
return instancesMap.get(tType) as T;
} else {
return new tType() as T;
}
}
getInstance
, , ,Reflect.getMetadata
, . any
, as Metadata
- , .
tType: new () => T
- - , Map
, , .
getInstance
, .
, . , Javascript .
. , , , . , Person
Age
, 18 60. :
class Person {
@Age(18, 60)
age: number;
}
:
declare type PropertyDecorator =
(target: Object, propertyKey: string | symbol) => void;
:
import 'reflect-metadata';
function Age(from: number, to: number) {
return function (object: Object, propertyName: string) {
const metadata = {
propertyName: propertyName,
range: { from, to },
};
Reflect.defineMetadata(`validationMetadata_${propertyName}`, metadata, object.constructor);
};
}
, . . , , , . , .
:
class Person {
...
}
__decorate([
age_decorator_1.Age(18, 60),
__metadata("design:type", Number)
], Person.prototype, "age", void 0);
, __decorate
, .
, — , . — , , .
, :
function validate<T>(object: T) {
const properties = Object.getOwnPropertyNames(object);
properties.forEach(propertyName => {
let metadata = Reflect.getMetadata(metaKey + propertyName, object.constructor);
if (metadata && metadata.range) {
const value = object[metadata.propertyName];
if (value < metadata.range.from || value > metadata.range.to) {
throw new Error('Validation failed');
}
}
});
}
, , . .
:
const person = new Person();
person.age = 40;
validate(person);
person.age = 16;
validate(person);
, , , .
, :
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
Class-Validator
Class-Validator, . .
export class Post {
@Length(10, 20)
title: string;
@IsInt()
@Min(0)
@Max(10)
rating: number;
@IsEmail()
email: string;
}
...
validate(object).then(errors => {
if (errors.length > 0) {
console.log("validation failed. errors: ", errors);
} else {
console.log("validation succeed");
}
});
, . .
, NestJS @UsePipes(new ValidationPipe())
http .
Typescript , . , -. , , , , , , , … Angular NestJS. ( ).
, , , , , !
ps en écrivant (ou plutôt en traduisant son propre article), j'en ai trouvé un autre juste là sur le hub, qui révèle bien le thème des décorateurs. Je donne un lien