Dekorator Jenis-Jenis yang Kuat - bagaimana cara kerjanya, ke mana mereka dikompilasi, dan untuk tugas aplikasi mana yang berlaku

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.






. , .


  • for Module declaration

@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- . .


:


  • target
  • reflect-metadata

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);
// > validation passed

person.age = 16;
validate(person);
// > validation error


, , , .


, :


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 => { // array of validation 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


All Articles