TypeScript - Decorators

Fala pessoal! Hoje vou falar sobre uma feature muito legal decorators. Utilizamos decorators para inserir metadados e comportamentos em uma declaração de classe, propriedade, métodos ou parâmetro de uma função. Trata-se de uma função com uma assunatura específica (de acordo com o target).

Para utilizar um decorator precisamos utilizar o simbolo @ junto com o nome do decorator antes do membro do código que estivermos decorando. Exemplo:

class MyClass {
    @log
    doSomething(arg) {
        //...
    }
}

NOTA: para utilizar esse recurso é necessário configurar a propriedade experimentalDecorators no arquivo tsconfig.json. Para compilar o arquivo via linha de comando utilize: tsc myFile.ts –target ES5 –emitDecoratorMetadata. s

Pontos importantes

Decorators em Métodos

Para definir um decorator para um méroto precisamos criar uma função com os seguintes parâmetros:

No primeiro exemplo eu utilizei um decorator chamado @log. Vamos ver uma possível implementação deste decorator:

function log(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
    let originalMethod = descriptor.value; // salvando uma referência para o método original

    // NOTE: não use arrou fynction. Utilize uma declaração normal de função para que o contexto `this` seja interpretado corretamente.
    descriptor.value = function(...args: any[]) {
        console.log("Argumentos da chamada: " + JSON.stringify(args));
        let result = originalMethod.apply(this, args);               // Executa a função e armazena o resultado
        console.log("Valor de retorno: " + result);               
        return result;                                               // retorna o resultado
    };

    return descriptor;
}

Chamando a função:

class MyClass {
    @log
    doSomething(arg) {
        return "Message -- " + arg;
    }
}

new MyClass().doSomething("test");
// => Argumentos da chamada: ["test"]
// => Valor de retorno: Message -- test

Agora que vimos como uma declaração simples de decorator funciona vamos entender como declarar decorators que esperam parâmetros. Para isso precisamos declarar uma função com a assinatura de parâmetros desejado e retornar uma outra função com a mesma assinatura do exemplo anterior. Veja o seguinte código:

function log(showArgs: boolean) {
    return function(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
        let originalMethod = descriptor.value; // salvando uma referência para o método original

        // NOTE: não use arrou fynction. Utilize uma declaração normal de função para que o contexto `this` seja interpretado corretamente.
        descriptor.value = function(...args: any[]) {
            if (showArgs) {
                console.log("Argumentos da chamada: " + JSON.stringify(args));
            }
            let result = originalMethod.apply(this, args);               // Executa a função e armazena o resultado
            console.log("Valor de retorno: " + result);               
            return result;                                               // retorna o resultado
        };

        return descriptor;
    }
}

Mais uma vez vamos chamar a função:

class MyClass {
    @log(false)
    doSomething(arg) {
        return "Message -- " + arg;
    }
}

new MyClass().doSomething("test");
// => Valor de retorno: Message -- test

NOTA: sempre que utilizarmos uma decorator em uma função estática o target será a propria função ou invés do protótipo da classe.

Decorators em Classes

A assinatura da função que define um decorator para uma classe possui apenas o parâmetro target. Veja o seguinte exemplo:

const __myClassDecoratorMetaData: any = {};

function MyClassDecorator(value: string) {
  return function (target: Function) {
      __myClassDecoratorMetaData[target] = value;
  }
}

@MyClassDecorator(“my metadata”)
class MyClass { }

var myClass = new MyClass();
let value: string = __myClassDecoratorMetaData[myClass.constructor];
console.log(value); //=> my metadata

Existe ainda a possibilidade de adicionar decorators em propriedades e argumentos de função. Tentarei cobrir estas outras duas possibilidades em outro artigo.

Até a próxima!


Programador .NET - TypeScript - JavaScript - Membro dos times DefinitelyTyped e TypeStrong.