Setiap pengembang Angular melihat dekorator dalam kode kode waktu. Mereka digunakan untuk menggambarkan Modul, mengkonfigurasi Injeksi Ketergantungan, atau mengkonfigurasi komponen. Dengan kata lain, dekorator digunakan untuk menjelaskan informasi tambahan, atau metadata, untuk kerangka kerja atau penyusun (dalam kasus Angular). Apalagi, Angular hanyalah salah satu contoh. Ada banyak perpustakaan lain yang menggunakan dekorator untuk kesederhanaan dan visualisasi kode, sebagai pendekatan deklaratif. Sebagai pengembang .NET di masa lalu, saya melihat banyak kesamaan antara dekorator TS dan atribut .NET. Akhirnya, kerangka kerja aplikasi backend NestJS yang berkembang (abstraksi atas Node) juga dibangun di atas penggunaan dekorator yang berat dan pendekatan deklaratif. Bagaimana cara kerjanya dan bagaimana menggunakan dekorator dalam kode Anda,untuk membuatnya lebih nyaman dan mudah dibaca? Kita semua mengerti bahwa setelah mengkompilasi kode TS, kita mendapatkan kode Javascript. Di mana tidak ada konsep dekorator, seperti banyak fitur Script lainnya. Oleh karena itu, bagi saya pertanyaan yang paling menarik adalah bagaimana bentuk dekorator setelah kompilasi. Mengejar masalah ini, saya berpidato di pertemuan di Minsk dan saya ingin berbagi artikel.

. , .
@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 saat menulis (atau lebih tepatnya menerjemahkan artikelnya sendiri), saya menemukan satu lagi di hub, yang juga mengungkapkan tema dekorator. Saya memberi tautan