Poderosos decoradores de texto datilografado - como eles funcionam, em que são compilados e para quais tarefas do aplicativo são aplicáveis

Cada desenvolvedor Angular viu decoradores no código do timecode. Eles são usados ​​para descrever módulos, configurar injeção de dependência ou configurar um componente. Em outras palavras, os decoradores são usados ​​para descrever informações adicionais, ou metadados, para uma estrutura ou compilador (no caso do Angular). Além disso, Angular é apenas um exemplo. Existem muitas outras bibliotecas que usam decoradores para simplificar e visualizar o código, como uma abordagem declarativa. Como desenvolvedor .NET, no passado, vejo muitas semelhanças entre decoradores TS e atributos .NET. Por fim, a crescente estrutura de aplicativos de back-end do NestJS (uma abstração sobre o Node) também é construída sobre o uso intenso de decoradores e uma abordagem declarativa. Como tudo funciona e como usar decoradores em seu código,para torná-lo mais conveniente e legível? Todos entendemos que, depois de compilar o código TS, obtemos o código Javascript. No qual não há conceito de decorador, como muitos outros recursos do Typcript. Portanto, para mim, a pergunta mais interessante é o que o decorador se transforma após a compilação. Na sequência desta edição, fiz um discurso na reunião em Minsk e quero compartilhar o artigo.






. , .


  • 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 enquanto escrevia (ou melhor, traduzia seu próprio artigo), encontrei outro bem ali, no hub, o que revela bem o tema dos decoradores. Eu dou um link


All Articles