red handle paint brush in white paint bucket

Explorando os decorators em TypeScript

Introdução aos Decorators em TypeScript

Definição de decorators

Antes de mergulharmos na aplicação em TypeScript, é essencial entender o que são decorators. Adotado de outros idiomas notáveis, como Python, os decorators são uma maneira de alterar ou estender o comportamento de uma função, método ou classe sem modificar o código original.

Code blocks podem facilitar esse conceito. Digamos que temos uma simples função que retorna um valor numérico:

function simple() {
  return 12;
}

Agora, o que acontece se queremos que esta função não retorne 12, mas sim o dobro disso, ou seja, 24? Uma maneira seria modificar a função original. No entanto, isso não é uma boa prática. Queremos manter a função original intacta. É aqui que os decorators entram em jogo.

function doubleDecorator(fn) {
  return function() {
    const originalResult = fn();
    return originalResult * 2;
  }
}

const decoratedSimple = doubleDecorator(simple);

console.log(decoratedSimple()); // outputs 24

O objetivo de um decorator é ‘embrulhar’ o comportamento original em um novo comportamento. Isso é feito retornando uma nova função que faz alguma coisa antes ou depois de chamar a função original. No nosso exemplo, pegamos o resultado da função simple e dobramos.

Esta ideia é poderosa e altamente reutilizável. Embora o exemplo dado seja bastante simplista, você pode imaginar que um decorator poderia ser usado para registrar quando uma função é chamada, para manipular erros lançados por uma função, ou para adicionar algum estado persistente a uma função.

Em suma, um decorator é uma função que pega uma função, método ou classe como input e retorna uma versão modificada da mesma. A aplicação deles permite que você estenda e personalise o comportamento do seu código sem alterar o código original, proporcionando uma separação clara de preocupações e tornando o seu código mais fácil de manter e de testar.

Benefícios de usar decorators em TypeScript

Em nossas incursões pelo vasto terreno da programação, muitas vezes nos deparamos com elementos que, à primeira vista, podem parecer obscuros ou desafiadores – os decorators em TypeScript são um exemplo perfeito. Contudo, não se deixe intimidar por sua aparente complexidade; uma vez que você compreenda sua essência e funcionalidade, você perceberá os incríveis benefícios que os decorators podem trazer para seu código.

Os decorators permitem que você adicione funcionalidades adicionais a classes, métodos, propriedades ou parâmetros sem alterar sua definição original. Facilita a manutenção e organização do código, pois as funcionalidades agregadas pelos decorators ficam segregadas do core do código. É como vestir seu código com um terno sob medida, que não altera a essência, mas acrescenta um charme extra e desempenho aprimorado.

Partindo para um exemplo prático, imagine que temos um conjunto de funções em nosso aplicativo que só devem ser executadas se um certo conjunto de condições for cumprido. Sem os decorators, teríamos que escrever a lógica de verificação em cada função ou criar uma função separada e chamá-la sempre que necessário. Isso poderia acabar tornando nosso código confuso e difícil de manter.

Agora, vamos ver como a mesma lógica poderia ser implementada usando decorators.

function ConditionsMet(condition: boolean): Function {
  return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    let originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      if(condition) {
        return originalMethod.apply(this, args);
      } else {
        console.log("Conditions not met, method not executed");
      }
    }
    return descriptor;
  }
}

class Example {
  @ConditionsMet(true)
  runMethod() {
    console.log("Method executed");
  }
}

Com este decorator definido, qualquer método que quisermos que execute sob certas condições, nós simplesmente podemos adorná-lo com @ConditionsMet(true). Dessa forma, nosso código fica mais limpo, mais fácil de manter e, além disso, nós adicionamos um pouco mais de modularidade ao nosso código, lindo não?

Mas isso é apenas uma pontinha do que você pode alcançar com os decorators em TypeScript. Você pode criar decorators para praticamente qualquer coisa – validação de dados, logging, memoization e muito mais. Cada alteração e melhoria de comportamento que você conseguir imaginar, é possível implementar com decoradores – e tudo isso sem afetar a base do seu código. Incrível, não é? Está pronto para começar a usá-los e levar seu código TypeScript a um novo nível?

Como decorators funcionam em TypeScript

Decorators, na essência, são uma maneira poderosa de aprimorar sua classe, propriedade, método ou parâmetro diretamente usando uma estrutura declarativa. Usando Decorators, é possível anexar metadados adicionais ou alterar o comportamento de uma classe, sem alterar diretamente seu código base.

Em TypeScript, essa funcionalidade poderosa é estendida de uma das características do JavaScript, que é a capacidade de alterar o comportamento de uma função ou classe em tempo de execução. No entanto, no TypeScript, Decorators oferecem um meio mais elegante e acessível de adicionar tais comportamentos.

Vamos dar uma olhada rápida nas diferentes formas de decorators que o TypeScript suporta:

  • Class Decorators: Anexado diretamente antes da declaração de uma classe.
  • Method Decorators: Anexado diretamente antes de um método de classe.
  • Property Decorators: Anexado diretamente antes de uma propriedade de classe.
  • Parameter Decorators: Anexado diretamente antes de um parâmetro de método de classe.

Agora, vamos a um exemplo prático para expor melhor o conceito. Imagine que temos uma classe Vehicle e queremos inspecionar todas as instâncias dessa classe quando são criadas. Poderíamos, então, usar um Class Decorator para realizar esta tarefa.

function inspect() {
  return function (target: any) {
    console.log(`A new instance of ${target.name} was created`, target);
  };
}

@inspect()
class Vehicle {
  constructor(public wheels: number, public color: string) {}
}

Neste exemplo, sempre que uma nova Instância de Vehicle for criada, ela será registrada no console. Este é um exemplo simples, porém serve para ilustrar o quão poderosos os Decorators podem ser, uma vez que eles têm a capacidade de alterar ou estender o comportamento de nossas classes, propriedades e métodos.

Agora você deve estar se perguntando, como o TypeScript executa essas operações de Decorator? Bom, quando um Decorator é anexado a uma classe, propriedade, método ou parâmetro, o TypeScript transpila esse código para JavaScript puro e registra a chamada do Decorator dentro da classe, propriedade, método ou parâmetro que está sendo decorado.

É importante lembrará que os Decorators são uma funcionalidade experimental de TypeScript e precisam ser ativados manualmente nas opções de configuração do (tsconfig.json):

{
  "compilerOptions": {
    "target": "ES5", 
    "experimentalDecorators": true
  }
}

Lembrando que TypeScript apenas adiciona sintaxe e tipagem a linguagem JavaScript, então, por baixo dos panos tudo será convertido para JS. Por isso, os decorators representam uma maneira muito útil de trabalhar com metaprogramação, ou seja, programas que escrevem ou manipulam outros programas (ou a si mesmos) como seus dados, pois permitem estender o comportamento das nossas classes, métodos, acessadores e propriedades de uma forma bastante elegante, declarativa e fluente.

Tipos de Decorators em TypeScript

Class Decorators

Seguindo adiante em nossa discussão sobre decorators em TypeScript, tomemos um momento para mergulhar mais fundo nos Class Decorators. Esse é um recurso avançado do TypeScript que provê uma maneira de adicionar anotações e uma sintaxe de metadados para classes.

Basicamente, um Class Decorator é uma expressão que define como alterar uma classe de destino e é chamada em tempo de execução com a classe construtora sendo seu único argumento. Eles são aplicados imediatamente depois que a classe é declarada e antes de qualquer membro de classe ser declarada.

No TypeScript, para aplicar um decorator, usamos um ‘@’. Vamos ver um exemplo:

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

Neste exemplo, o decorator @sealed está sendo aplicado à classe Greeter. O decorator sealed irá prevenir que novas propriedades sejam adicionadas à classe Greeter e marcará todas as propriedades existentes como não configuráveis.

Assim, vemos que os Class Decorators fornecem uma maneira elegante de estender e modificar o comportamento de uma classe. No entanto, é importante notar que os decorators são apenas uma proposta para o JavaScript e ainda não são nativamente suportado por muitos navegadores. Portanto, eles devem ser transpilados para JavaScript regular antes de serem executados no navegador.

Na ordem certa de aplicação, quando múltiplos decorators são aplicados numa única classe, eles são aplicados do último para o primeiro (aplicado de baixo para cima).

Os Class Decorators parecem ser algo complexo quando você se depara com eles pela primeira vez, mas são extremamente úteis. Com prática e período de tempo usando-os, você começará a ver o seu valor ao trabalhar com TypeScript.

Method Decorators

Depois de ter explorado os conceitos básicos dos decorators em TypeScript, você deve estar ansioso para mergulhar um pouco mais fundo. Então, hoje, vamos nos concentrar nos Method Decorators.

Os Method Decorators, como o próprio nome sugere, são aplicados aos métodos de uma classe. Eles são usados para observar, modificar ou substituir um método do tipo de método.

Para entender melhor, considere este exemplo simples:

function LogMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log(`Method Decorator called on: ${key}`);
}

class MyClass {
  @LogMethod
  myMethod() {
    console.log('Hello World!');
  }
}

let myClass = new MyClass();
myClass.myMethod();

No exemplo acima, @LogMethod é o nosso Method Decorator. Quando myMethod() é chamado, o decorador LogMethod é ativado primeiro e a mensagem “Method Decorator called on: myMethod” é registrada no console. Então a mensagem ‘Hello World!’ é impressa.

O procedimento de decoração ocorre como tal: o TypeScript verifica se há algum decorator no método quando a classe é instanciada e, se houver, ele é executado antes de entrar na própria função.

Os Method Decorators têm acesso a três informações:

  • target: A instância da classe
  • key: O nome do método ao qual o decorator é aplicado
  • descriptor: Um objeto que descreve as propriedades do método

Essas informações podem ser usadas para modificar o comportamento do método. Por exemplo, você pode alterar o valor de retorno, alterar a implementação do método, fazer log do acesso ao método e muito mais.

Podemos alterar a implementação do método do exemplo para o seguinte:

function LogMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  let originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method ${key} called with args: ${JSON.stringify(args)}`);
    let result = originalMethod.apply(this, args);
    console.log(`Result: ${JSON.stringify(result)}`);

    return result;
  };

  return descriptor;
}

class MyClass {
  @LogMethod
  sum(a: number, b: number) {
    return a + b;
  }
}

let myClass = new MyClass();
console.log(myClass.sum(1, 2));

Agora você deve ter uma compreensão clara dos Method Decorators e como eles podem ser usados para modificar nossas funções, o que as torna uma excelente ferramenta de metaprogramação.

No próximo texto, trataremos de outro tipo de decorator, Accessor Decorator, e como podemos usar os decorators para ter um código TypeScript mais limpo e mais eficiente.

Accessor Decorators

Decorator é uma lógica de programação avançada em TypeScript que permite adicionar funcionalidades a um objeto em tempo de execução, sem subverter completamente os outros recursos da classe. Com ele, você pode estender ou controlar o comportamento de uma propriedade ou método. Dentro do ecossistema dos decorators, um tipo valioso e fascinante são os Accessor Decorators.

Então, o que são os Accessor Decorators? De forma simples, eles são usados com acessores de propriedades, ou seja, com os ‘getters’ e ‘setters’ de uma classe. Estes decorators podem editar, ou realizar lógicas complexas durante a chamada de um get ou set. Desta forma, eles nos permitem controlar a maneira que esses acessores são utilizados.

Vamos a um exemplo prático para ilustrar como um Accessor Decorator poderia ser utilizado.

function AccessorDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
    console.log(`Accessor Decorator called on: 
      target: ${JSON.stringify(target)},
      key : ${key},
      descriptor : ${JSON.stringify(descriptor)}`);
}

class MyClass {
    private _myProp: string;
    constructor( str: string) {
      this._myProp = str;
    }

    @AccessorDecorator
    get myProp(): string {
      return this._myProp;
    }

    set myProp( str: string) {
      this._myProp = str;
    }
}

Neste exemplo, nós definimos um decorator, AccessorDecorator, que imprime algumas informações quando um acessor getter da propriedade ‘_myProp’ de ‘MyClass’ é chamado. Note que é o decorador que está anexado ao getter, e não a propriedade em si: isso é uma distinção importante!

Os Accessor Decorators podem ser extremamente poderosos. No entanto, é importante também ter cautela quando os utiliza. Com eles, você pode alterar significativamente o comportamento de uma classe sem alterar o código base dessa classe. Além disso, eles podem ser uma fonte de confusão, pois eles encapsulam comportamentos que não são imediatamente aparentes ao ler o código de uma classe. Use com cuidado!

Property Decorators

Assim como você deve ter visto em tópicos anteriores, os decorators em TypeScript podem ser muito úteis para adicionar behaviors personalizados de uma forma limpa e reutilizável para suas classes e métodos.

Agora, vamos dar um passo adiante e explorar os Property Decorators. Um Property Decorator é simplesmente um tipo especial de decorator que pode ser aplicado a propriedades de classe.

Então, como eles funcionam? Bem, também são funções que podem usar para anotar ou modificar classes e membros de classes. No entanto, ao contrário dos method decorators, os property decorators são aplicados diretamente nas propriedades de uma classe.

Suponha que você tenha a seguinte classe:

class Person {
    name: string;
}

Se você quer adicionar algum comportamento extra à propriedade “name”, você pode fazer isso com um property decorator assim:

function Log(target: any, key: string) {
    console.log(`${key} foi chamado.`);
}

class Person {
    @Log
    name: string;
}

No exemplo acima, utilizamos o decorator @Log para imprimir uma mensagem no console cada vez que a propriedade “name” for chamada. Se você criar uma nova instância da classe Person e acessar a propriedade name, você verá a mensagem do console.

Os decorators de propriedades oferecem muita flexibilidade, permitindo que você controle o comportamento das propriedades da sua classe de uma maneira intuitiva e fácil de gerenciar. Imagine uma situação em que você precisa validar o valor de uma propriedade sempre que ela for alterada, ou talvez você queira fazer log sempre que uma propriedade é acessada. Com os property decorators, isso se torna uma tarefa relativamente simples.

Como todos os decorators em TypeScript, os property decorators podem ser uma ferramenta poderosa no seu arsenal de desenvolvimento, então aproveite ao máximo!

Parameter Decorators

Quando começamos a trabalhar com Parameter Decorators, a primeira coisa que salta à vista é a sua versatilidade. Para quem está se introduzindo no universo do TypeScript e quer explorar as profundezas de suas funcionalidades, os Parameter Decorators atuam como uma poderosa ferramenta de personalização e extensão da linguagem.

Antes de mais nada, vale destacar que os Parameter Decorators são aplicados diretamente aos parâmetros das declarações de métodos, construtores e assinaturas de métodos de acesso do TypeScript. Diferente dos class decorators, property decorators ou method decorators, eles operam diretamente sobre os parâmetros, proporcionando uma abordagem granular e direta para incrementar ou modificar suas funcionalidades.

Aqui fica um exemplo prático de como um Parameter Decorator pode ser usado:

function Log(target: any, key : string, parameterIndex : number ) {
  console.log(`The ${key} method has been called with parameter index ${parameterIndex}.`);
}

class Person {
  greet(@Log name: string, @Log age: number) {
    console.log(`Hello ${name}, you are ${age} years old.`);
  }
}

let person = new Person();
person.greet('John', 25);

Nesse exemplo, sempre que a função greet for chamada, o decorador @Log será disparado para cada parâmetro e irá escrever no console a chave do método e o índice do parâmetro.

Isto posto, os Parameter Decorators podem ser um importante instrumento na hora de depurar o seu código ou dar mais poder ao seu comportamento, permitindo que você aja diretamente nos parâmetros do método, construtor ou assinatura de método. Lembre-se de sua função básica: realizar operações adicionais e personalizadas, mas sem alterar a semântica original do programa. Se bem utilizados, podem tornar o seu código mais limpo, mais fácil de ler e de manter.

Usando Decorators em TypeScript

Como usar class decorators

Olá! Que tal aprofundarmos juntos em um recurso fantástico do TypeScript, os Class Decorators? Esta é uma ferramenta poderosa que permite adicionar funcionalidades extras a nossas classes de maneira limpa e organizada. Vamos juntos passear por este território tão empolgante?

Decorators, de maneira geral, são uma forma de envolver uma função, método, acessor, propriedade ou parâmetro e estender seu comportamento sem alterar seu código. Eles são invocados quando uma declaração decorada é definida, não quando ela é chamada.

Quando se trata de class decorators, especificamente, eles são aplicados ao construtor da classe e podem ser usados ​​para monitorar, modificar ou substituir as definições de classe. Para criar um class decorator, definimos uma função que recebe uma única argumento: o construtor da classe.

Vamos a um exemplo prático. Digamos que tenhamos uma classe chamada “Car” e queremos monitorar as instâncias dessa classe que estão sendo geradas. Poderíamos usar um class decorator para fazer isso.

``TypeScript function instanceTracker(constructor: Function) { console.log(Uma instância da classe ${constructor.name} foi criada.`);
}

@instanceTracker
class Car {
}
“`

No código acima, definimos o decorator “instanceTracker”. Esse decorator recebe o construtor da classe como argumento e registra uma mensagem no console quando uma instância da classe “Car” é criada.

Agora, sempre que criamos uma nova instância da classe “Car”, o decorator “instanceTracker” vai registrar a mensagem. Isso pode ser particularmente útil, por exemplo, se quisermos monitorar a criação de novas instâncias durante a depuração.

“`TypeScript
else {
new Car(); // Saída: Uma instância da classe Car foi criada.
new Car(); // Saída: Uma instância da classe Car foi criada.
}
“`

No entanto, essa apenas arranhamos a superfície do que podemos fazer com os Class Decorators. Na verdade, eles podem ser usados para fazer coisas muito mais interessantes, como adicionar propriedades estáticas, definir herança entre classes e muitas outras coisas.

É importante notar, no entanto, que os decorators não são parte do padrão JavaScript (ainda!). Embora estejam propostos para adição futura ao JavaScript, por enquanto eles são um recurso experimental do TypeScript.

Então, aventure-se com cautela! Enquanto os decorators adicionam muitas oportunidades interessantes, também abrem portas para complexidades desnecessárias. Use com sabedoria!

Espero que você tenha encontrado essa introdução aos Class Decorators útil! Continue lendo para mergulhar ainda mais fundo neste e em muitos outros tópicos emocionantes no TypeScript.

Como usar method decorators

Em nosso passeio pelos intricados caminhos do TypeScript, hoje vamos abordar um tópico fascinante: o uso de method decorators. Mas, você deve estar pensando, o que são method decorators?

Os method decorators, em sua essência, são uma maneira de adicionar, modificar ou manipular métodos de uma classe em tempo de execução. O principal uso é adicionar metadados adicionais a uma classe ou método, ou modificar o comportamento de um método já existente.

Então, como você pode implementar um method decorator? Primeiro, você precisa entender que um decorator é apenas uma função. Eles têm uma sintaxe especial em TypeScript, onde são precedidos por um sinal de “@” e acoplados diretamente a um método, como no exemplo a seguir:

class MyClass {
  @myDecorator
  myMethod() {}
}

Note que myDecorator é apenas uma função que você define em algum lugar do seu código. Essa função será chamada em tempo de execução pelo TypeScript, e ela pode fazer praticamente qualquer coisa que possa imaginar.

A assinatura para um method decorator em TypeScript é a seguinte:

function(target: Object,  
        propertyKey: string | symbol, 
        descriptor: TypedPropertyDescriptor<any>)
  • target: É o protótipo da classe onde o método decorado está.
  • propertyKey: É o nome do método que está sendo decorado.
  • descriptor: É a descrição do método que pode ser usada para modificar o comportamento do método.

Agora, vamos ver como podemos usar isso para modificar o comportamento de uma classe. Imagine que temos a seguinte classe:

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }

Agora, vamos criar um decorator que irá modificar o método greet para sempre adicionar um “!” ao final da saudação:

function decorateGreeting(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
  let originalMethod = descriptor.value;

  descriptor.value = function() {
    let result = originalMethod.apply(this);
    return result + "!";
  };

  return descriptor;
}

Agora, vamos aplicar nosso decorator na classe Greeter:

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @decorateGreeting
  greet() {
    return "Hello, " + this.greeting;
  }
}

Agora, sempre que chamarmos o método greet, ele irá adicionar um “!” ao final da saudação:

let greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world!" 

Como pode ver, os method decorators são poderosos e flexíveis, permitindo que você manipule e estenda o comportamento dos métodos de sua classe de várias formas.

Como usar accessor decorators

Agora que estamos mais familiarizados com a ideia geral dos Decorators, vamos dar um passo adiante e explorar um tipo específico de decorator – o Accessor Decorator.

Os Accessor Decorators são aplicados a get e set acessors de uma propriedade. Eles permitem que possamos adicionar comportamentos extras aos acessors sem alterar sua funcionalidade principal.

Vamos primeiro definir alguns termos:

  • Acessor de propriedade: Em TypeScript, usamos get e set para obter ou definir o valor das propriedades de um objeto. Estes são conhecidos como acessores de propriedade.
class Example {
  private _value: number = 0;

  get value(): number {
    return this._value;
  }

  set value(newValue: number) {
    this._value = newValue;
  }
}

Agora, digamos que você queira executar alguma lógica sempre que valor for definido, como, por exemplo, registrar a alteração do valor. Aqui estão os Accessor Decorators em ação.

Um decorator de acessor é declarado antes da declaração de um acessor de propriedade. Ele é aplicado ao descritor de propriedade e pode ser usado para observar, modificar ou substituir uma definição de acessor de propriedade.

Olhe este exemplo de como criar um Accessor Decorator:

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  let originalSet = descriptor.set;

  descriptor.set = function(value: any) {
    // Aqui logamos a mudança de valor.
    console.log(`Value for ${propertyKey} changed to ${value}`);

    // O valor original é setado.
    originalSet.call(target, value);
  }

  return descriptor;
}

E aqui está como usaríamos este decorator em um accessor de propriedade:

class Example {
  private _value: number = 0;

  @Log
  set value(newValue: number) {
    this._value = newValue;
  }
}

Entendeu como funciona? Agora, sempre que chamamos example.value = 5, a mensagem “Value for value changed to 5” seria exibida no console, devido ao nosso Accessor Decorator.

Como pode observar, os Accessor Decorators podem ser bastante úteis para adicionar lógica extra aos seus acessors sem mudar o código original dos mesmos. Isso pode facilitar o acompanhamento de alterações de estados sem adicionar complexidade desnecessária ao seu código.

Na próxima seção, vamos continuar explorando os Decorators e como eles podem melhorar ainda mais nossos códigos em TypeScript. Fique ligado!

Como usar property decorators

No mundo da programação, os decorators podem oferecer uma maneira eficiente de adicionar funcionalidades a seus métodos ou propriedades. Então, vamos falar sobre como usar os property decorators.

Um property decorator é aplicado diretamente a uma propriedade específica. Ele lhe permite adiciona, substituir ou modificar o comportamento de uma propriedade. Da mesma forma que os métodos decorators, os property decorators são chamados quando a propriedade é definida, não quando ela é acessada.

Imagine que você tem uma classe chamada ‘Carro’. Esta classe tem várias propriedades, como marca, modelo e ano. Agora, vamos supor que você queira adicionar uma funcionalidade que converte a marca para caixa alta quando ela é definida. Você poderia fazer algo assim:

function toUpperCase(target: any, key: string) {
  let value = this[key];

  const getter = () => value;
  const setter = (next) => {
    value = next.toUpperCase();
  };

  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
  });
}

class Carro {
  @toUpperCase
  marca: string;

  constructor(marca : string) {
    this.marca = marca;
  }
}

let meuCarro = new Carro("audi");
console.log(meuCarro.marca); // Auditiona "AUDI" 

Neste exemplo, criamos um property decorator chamado ‘toUpperCase’. Este decorator pega a propriedade que ele está decorando e redefine-a com a função ‘setter’ personalizada. Esta função ‘setter’ recebe o valor da propriedade e modifica-o para ser retornada em caixa alta.

A coisa mais importante a lembrar sobre o uso de property decorators é que eles permitem que você interaja diretamente com a propriedade que eles estão decorando.

Isso dá a você uma enorme quantidade de controle sobre a forma como essas propriedades são usadas e permite que você adicione funcionalidades complexas com muito pouco código. Então, da próxima vez que você estiver procurando uma maneira elegante de modificar uma propriedade, considere o uso de um property decorator!

Como usar parameter decorators

Os parâmetros decorators são um tipo de decorator em TypeScript que são aplicáveis aos parâmetros de um método ou, em situações menos comuns, de um construtor de classe. Como os outros tipos de decorators, eles contribuem para a criação de um código mais enxuto, evitando repetições desnecessárias e permitindo maior facilidade na manutenção e organização do código.

Antes de nos aprofundarmos em como você pode usar os parameter decorators, é importante salientar que os decorators são avaliados e chamados quando a classe é declarada, e não quando uma instância dessa classe é criada ou um método é chamado. Isso tem implicações importantes na definição e no uso de decorators.

Vamos analisar um exemplo simples de um parameter decorator:

function Log(target: Object, propertyKey: string, parameterIndex: number) {
  let existingParameters: number[] = Reflect.getOwnMetadata(propertyKey, target) || [];
  existingParameters.push(parameterIndex);
  Reflect.defineMetadata(propertyKey, existingParameters, target);
}

class MyClass {
  method(@Log param1: string, @Log param2: number) {
    console.log(`param1: ${param1}, param2: ${param2}`);
  }
}

Neste exemplo, Log é um parameter decorator que registra metadados sobre os parâmetros que ele decora. O decorator é aplicado a dois parâmetros do método method: param1 e param2.

Em TypeScript, os parameter decorators têm três argumentos:

  • target: O protótipo do objeto de classe.
  • propertyKey: O nome do método.
  • parameterIndex: O índice do parâmetro na lista de parâmetros do método.

No exemplo acima, o decorator Log registra o índice de cada parâmetro que decora, associando-o ao nome do método e o protótipo do objeto de classe. Dessa forma, você pode recuperar esses metadados mais tarde usando Reflect.getMetadata().

Os parâmetros decorators permitem que você incremente a funcionalidade de parâmetros específicos, aproveitando a metaprogramação para registrar ou modificar informações sobre esses parâmetros. Enquanto os parâmetros decorados por si só não alteram a funcionalidade do método ou classe, eles fornecem um mecanismo para associar metadados a parâmetros específicos, que podem ser usados por outros aspectos de seu código.

Exemplos práticos com Decorators

Exemplo de uso de class decorator

Decorators em TypeScript, para quem não está familiarizado, são uma maneira poderosa e flexível de modificar o comportamento de classes, métodos ou propriedades sem alterar a própria implementação original. Eles são muito semelhantes aos Decorators em Python e aos Anotations em Java. Vamos ver agora um exemplo de uso de Class Decorator.

Para começar, imagine que temos uma classe chamada `MyClass`:

typescript
class MyClass {
public prop: string;

constructor(prop: string) {
    this.prop = prop;
}

}

Este é um simples exemplo de uma classe que tem uma única propriedade chamada `prop`.

Agora, vamos criar um Class Decorator. Um Class Decorator em TypeScript é uma função que tem um único parâmetro, que é o construtor da classe a ser decorada.

typescript
function MyClassDecorator(constructor: T) {
return class extends constructor {
public newProp = “I am a new property added by MyClassDecorator”;
}
}

Neste exemplo, `MyClassDecorator` é um Class Decorator que returna uma nova classe que se estende da classe original, e adiciona uma nova propriedade chamada `newProp`. 

Finalmente, vamos aplicar o nosso Class Decorator para `MyClass`:

typescript
@MyClassDecorator
class MyClass {
public prop: string;

constructor(prop: string) {
    this.prop = prop;
}

}

Com isso feito, se criarmos uma instância de `MyClass`, ela terá a propriedade `newProp`, que foi adicionada pelo nosso Class Decorator:

typescript
let myInstance = new MyClass(“some value”);
console.log(myInstance.newProp); // Output: “I am a new property added by MyClassDecorator”

Como visto, os Class Decorators proporcionam uma maneira poderosa de adicionar um comportamento dinâmico às nossas classes de maneira reutilizável e flexível.

Exemplo de uso de method decorator

Vamos falar agora sobre um exemplo de uso de method decorator em TypeScript. Para quem ainda não está familiarizado, um method decorator nada mais é do que uma maneira eficaz e elegante de se adicionar ou alterar a funcionalidade de métodos de uma classe. No TypeScript, eles são declarados antes do nome do método a que estão associados.

Imagine, por exemplo, que temos uma classe Car que tem um método accelerate. No entanto, queremos adicionar uma funcionalidade que imprima o log sempre que o método accelerate for chamado. Eis aqui um exemplo de como isso poderia ser feito com o uso do method decorator:

function logMethod(target: any, key: string, descriptor: any) {
    let originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        console.log(`O método ${key} foi chamado com os parâmetros ${JSON.stringify(args)}`);
        return originalMethod.apply(this, args);
    };

    return descriptor;
}

class Car {
    speed: number;

    constructor(speed: number) {
        this.speed = speed;
    }

    @logMethod
    accelerate(increaseBy: number) {
        this.speed += increaseBy;
    }
}

All the code above does is create a decorator named logMethod. This decorator, when placed in front of a method declaration, overrides that method with a function that does exactly what the original method did (by calling it via originalMethod.apply(this, args)), but also logs some message when the method is called. When the accelerate() method inside the Car class is called, the message is logged, then the original method is executed.

Espero que você tenha encontrado este exemplo elucidativo. A beleza dos decorators reside em sua capacidade de facilitar a adição ou alteração de comportamentos sem precisar alterar os métodos base de uma classe. Continue lendo para se aprofundar ainda mais no assunto!

Exemplo de uso de accessor decorator

Para entender melhor os decorators, vamos analisar um exemplo com o uso do accessor decorator. Os Accessor Decorators são usados com getters e setters em TypeScript. Eles permitem que apliquemos uma lógica extra antes de um valor de propriedade ser definido ou antes que um valor de propriedade seja retornado.

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalDescriptor = descriptor.get;

    descriptor.get = function () {
        console.log(`Getting ${propertyKey}`);
        return originalDescriptor.apply(this);
    };

    return descriptor;
}

class Book {
    private _title: string;

    constructor(title: string) {
        this._title = title;
    }

    @Log
    get title(): string {
        return this._title;
    }

    set title(value: string) {
        this._title = value;
    }
}

Neste exemplo, temos uma classe Book que possui uma propriedade _title. Agora, o objetivo é logar algo sempre que o getter para a propriedade title é chamado. Para isso, criamos o decorator Log.

O decorator Log aceita três parâmetros: target, propertyKey e descriptor. O target é o protótipo do objeto, o propertyKey é o nome do acessador e o descriptor é um objeto que descreve o acessador.

Dentro do Log, substituímos o descriptor.get original por uma nova função que logs a mensagem Getting ${propertyKey} antes de retornar o valor original.

Assim, sempre que o getter da propriedade title for chamado, a mensagem será logada. É uma maneira elegante e convenientemente encapsulada de adicionar algum comportamento adicional à maneira como uma propriedade é acessada em um objeto.

Decorators, como o Log que criamos aqui, podem ser extremamente úteis para adicionar lógica extra ao acesso de propriedades de um objeto sem temperar com o código original do objeto. Isso ajuda a manter a lógica limpa e fácil de seguir, fornecendo ao mesmo tempo uma função poderosa e reutilizável.

Exemplo de uso de property decorator

O Property decorator é uma categoria específica de decorators no TypeScript que nos oferece uma grande flexibilidade para adicionar comportamentos adicionais às propriedades de nossos objetos ou classes. Neste texto, vamos abordar um exemplo prático de como utilizá-las para enriquecer o nosso código.

Considere que temos uma classe chamada Employee, que possui propriedades como firstName e lastName:

class Employee {
    firstName: string;
    lastName: string;
}

Agora, nós decidimos que queremos ter uma propriedade fullName que concatena o primeiro e último nome. Poderíamos, é claro, criar um método getFullName(), mas os decorators nos permite fazer isso de uma maneira muito mais elegante e intuitiva.

class Employee {
    firstName: string;
    lastName: string;

    @property
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

Note que utilizamos o decorator @property antes do getter de fullName. Isso indica que fullName agora tem um comportamento adicional: toda vez que a propriedade fullName for acessada, ela automaticamente retornará a concatenação de firstName e lastName.

Dessa forma, se criarmos um novo Employee e tentarmos pegar o seu fullName, teríamos:

let emp = new Employee();
emp.firstName = 'Jon';
emp.lastName = 'Doe';

console.log(emp.fullName);  // 'Jon Doe'

Fantástico, não é? Esta é a famosa capacidade de metaprogramação oferecida pelos decorators, permitindo-nos adicionar comportamentos extras de forma elegante e flexível. Utilizar o Property decorator nos permite economizar o tempo que gastaríamos criando getters e setters manualmente. Espero que, com este exemplo, você tenha percebido a utilidade e o potencial que os decorators possuem para melhorar e simplificar seu código.

Exemplo de uso de parameter decorator

No universo de TypeScript, os decorators nos ajudam a adicionar anotações e metadados para algumas classes. Agora, vamos aprofundar nossa compreensão abordando o uso do decorator de parâmetro. Esse tipo de decorator é útil quando você deseja decorar um parâmetro de um método ou construtor.

Pense no decorator de parâmetro como um wrapper, que adiciona funcionalidades ou informações adicionais a um parâmetro de uma função. Quando um decorator de parâmetro é aplicado a um parâmetro específico, ele é chamado cada vez que o método ou constructor é chamado.

Para dar uma imagem clara disso, vamos aplicar um decorator de parâmetro em um exemplo. Digamos que temos uma classe Book com um método create.

class Book {
  create(@logParameter title: string): Book {
    // implementation details
  }
}

Aqui, o @logParameter é um decorator aplicado ao parâmetro title. Quando o método create é chamado, este decorator será chamado primeiro.

Agora vamos definir este decorator logParameter. Os decorators de parâmetro são funções que recebem três argumentos: o alvo (i.e., a instância da classe ou o construtor da classe para um parâmetro estático), o nome da propriedade do método ou do construtor e o índice do parâmetro no array de parâmetros.

function logParameter(target : Object, propertyKey : string, parameterIndex : number) {
  console.log(`LogParameter Decorator called on: 
    ${propertyKey} argument index: ${parameterIndex} 
    in ${target}`);
}

Este exemplo simples mostra como um decorator de parâmetro pode ser usado para registrar o nome da função e o índice do parâmetro ao qual está associado. Embora este exemplo seja bastante básico, ele demonstra o poder e a flexibilidade oferecidos pelos decoradores de parâmetro em TypeScript.

Os decorators podem ser úteis em uma variedade de cenários, como logging, memoization, ou adição de metadados. Ao dominar os decorators, você pode economizar tempo e esforço, aumentar a legibilidade do seu código e facilitar o gerenciamento do código TypeScript.

Esperamos que isso lhe tenha dado uma visão clara da utilização do decorator de parâmetro. Conhecer os diferentes tipos de decorators e como aplicá-los em sua codificação diária melhorará imensamente suas habilidades de TypeScript.

Conclusão

Por que os decorators são relevantes em TypeScript

Decorators em TypeScript são uma funcionalidade extremamente útil e poderosa, embora possam ser um pouco complexas de entender inicialmente. Elas nos permitem adicionar metadados, propriedades ou métodos a um objeto de maneira elegante e intuitiva. Há uma série de razões pelas quais os decorators são relevantes em TypeScript e úteis para qualquer desenvolvedor TypeScript.

Primeiramente, os decorators proporcionam uma maneira extremamente flexível de alterar o comportamento de uma classe, método, acessador, propriedade ou parâmetro em tempo de execução. Por exemplo, você pode usar um decorator para adicionar um log sempre que uma determinada função é chamada, sem modificar o código da função em si. Isso pode ser útil para depuração, ou para monitorar o comportamento do seu código.

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    let originalFunction = descriptor.value;

    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${propertyKey} with ${JSON.stringify(args)}`);
        return originalFunction.apply(this, args);
    }
}

class MyClass {
    @log
    doSomething(arg1: string, arg2: number) {
        // Some logic here
    }
}

Neste exemplo, o decorator @log adiciona um log ao método doSomething, que será executado sempre que o método for chamado.

Em segundo lugar, os decorators em TypeScript oferecem uma maneira de “decorar” ou “marcar” classes ou propriedades com metadados, sendo assim, podem ser úteis em casos de uso de bibliotecas externas. Um exemplo popular é o uso de decorators no framework Angular, em que são utilizados para marcar e configurar as classes de components, directives, entre outros.

Por último, mas certamente não menos importante, os decorators permitem a reutilização de código. Ao definir um decorator, você pode reutilizá-lo em vários locais do seu código. Isso ajuda a manter o seu código DRY (Don’t Repeat Yourself), limpo e organizado.

Para concluir, os decorators trazem muitos benefícios para a codificação em TypeScript. Eles promovem o encapsulamento eficaz do código, aumentam a reutilização do código e permitem uma organização mais clara da lógica do código. Com a prática, você descobrirá que os decorators podem facilitar muito o seu trabalho de desenvolvimento e tornar seu código muito mais elegante e fácil de manter.

Circunstâncias comuns para o uso de decorators

Ao longo deste texto, examinamos várias maneiras de aplicar decorators em TypeScript. Agora, você pode estar se perguntando: em quais situações do dia a dia os decorators são úteis? Vamos explorar algumas delas.

Autenticação e Autorização

No desenvolvimento de aplicativos, a autenticação e autorização são necessárias com frequência. Os decorators podem ser usados para envolver funções de acesso a dados ou rotas de API e verificar se um usuário está autenticado ou se ele tem as permissões necessárias para acessar um recurso. Por exemplo:

@requireAuth
function getPrivateData(req, res) {
  // Lógica para retornar dados privados
}

O decorator @requireAuth pode verificar se há um token de autenticação válido e se o usuário tem as permissões necessárias para acessar getPrivateData.

Logging

Decorators também podem ser úteis para implementar funcionalidades de logging em seu código. Em vez de espalhar funções de log em todo o seu código, você pode criar um decorator que registra todas as informações necessárias sempre que um método é chamado. Veja um exemplo:

@logExecutionTime
function complexCalculation(a, b) {
  // Código para uma cálculo complexo
}

Neste exemplo, o decorator @logExecutionTime pode registrar o tempo que complexCalculation leva para ser executado.

Validação de Dados

Outro uso comum dos decorators está na validação de dados. Podem ser usados para adicionar verificações de validação a campos de classe ou métodos, proporcionando uma forma limpa e reutilizável para garantir a integridade dos dados.

class Product {
  @IsPositive()
  price: number;
}

Neste exemplo, o decorator @IsPositive verifica se o valor do preço é um número positivo.

Então aí está. Decorators em TypeScript são uma ferramenta flexível e poderosa para adicionar funcionalidades ao seu código de maneira clara e reutilizável. À medida que você continua explorando TypeScript, espero que você ache útil integrá-los ao seu conjunto de ferramentas.

Dicas para melhor uso dos decorators em TypeScript

Para aproveitar ao máximo os decorators em TypeScript e evitar possíveis armadilhas, existem algumas orientações que você pode seguir.

Mantenha os decorators simples

Um decorator deve ter apenas uma responsabilidade. Isso facilita a manutenção e a reutilização do código. Por exemplo, um decorator que registra o tempo de execução de um método deve fazer apenas isso. Qualquer lógica adicional deve ser movida para outro decorator.

 function methodTimeLogger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    ... // lógica para registrar o tempo de execução
}

Evite dependências circulares

Procure não ter decorators que dependem uns dos outros, pois isso pode levar a erros difíceis de depurar e potencialmente tornar seu programa num todo indeterminado.

Use decorators para separação de preocupações

Os decorators podem ser uma excelente ferramenta para separar as preocupações do seu código. Por exemplo, você pode criar um decorator para registrar logs de métodos e propriedades sem ter que espalhar a lógica de logging por todo o código.

function methodLogger(target:any, propertyKey: string, descriptor: PropertyDescriptor) {
    ... // lógica para registrar logs
}
class MyClass {
    @methodLogger
    myMethod() {
        ...
    }
}

Teste seus decorators

Assim como qualquer outra parte do seu código, os decorators devem ser testados para garantir que funcionem corretamente. Você pode fazer isso criando classes fictícias e aplicando os decorators a elas, em seguida, verificando se eles se comportam conforme esperado.

Evite o uso excessivo de decorators

Decorators são uma ferramenta poderosa, mas como qualquer ferramenta poderosa, eles devem ser usados com moderação. O uso excessivo de decorators pode tornar o seu código confuso e difícil de manter, além de potencialmente levar a problemas de desempenho.

Lembre-se, o melhor uso dos decorators em TypeScript é aquele que facilita e melhora a qualidade do seu código, permitindo que você mantenha a lógica de negócios separada da lógica não relacionada adicional. Eles são uma ferramenta, não uma solução para todos os problemas, então use-os com sabedoria.

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *