TypeScript é uma linguagem de programação que se baseia no JavaScript, mas com tipagem forte, o que possibilita a criação de ferramentas mais eficazes, especialmente em projetos de grande escala. O TypeScript surgiu para solucionar algumas das dificuldades encontradas ao escrever código em JavaScript, utilizando tipos para evitar erros comuns.
Em um código-fonte TypeScript, cada valor possui um tipo específico. O TypeScript garante que cada valor siga as regras do seu tipo correspondente, identificando erros no código antes mesmo da execução do programa. Essa prática é conhecida como verificação de tipo estático.
A verificação de tipo estático analisa o código e detecta erros baseados nos tipos de dados utilizados, antes mesmo da execução do código.
Além de auxiliar na criação de código mais claro e legível e oferecer a verificação de tipo estático, o TypeScript possui funcionalidades que aumentam a legibilidade, a reutilização e a manutenção do código. Uma dessas funcionalidades são os *decorators* (decoradores) do TypeScript.
Decoradores TypeScript
Decoradores em TypeScript são um recurso que permite modificar o comportamento do seu código em tempo de execução, bem como adicionar metadados a ele. Eles possibilitam a metaprogramação, uma técnica em que os programas podem tratar outros programas como dados, modificando assim seu comportamento.
Basicamente, os decoradores são funções que são executadas para aplicar alguma lógica quando os elementos decorados são acessados ou modificados. Isso permite adicionar novas funcionalidades aos elementos. Os decoradores TypeScript podem ser aplicados a declarações de classes, métodos, propriedades, *accessors* (getters e setters) e parâmetros de método.
Em TypeScript, os decoradores são precedidos pelo símbolo @, como em `@expressao`, onde a expressão é avaliada para uma função que será executada em tempo de execução. A sintaxe geral para uso de decoradores é:
@nomeDoDecorador elementoADecorar
Veja um exemplo de um decorador de classe simples:
function logClass(alvo: Function) { console.log("O Decorador de Classe Log foi chamado"); console.log("Classe:", alvo); } @logClass // @logClass é um decorador class MinhaClasse { constructor() { console.log("Uma instância de MinhaClasse foi criada"); } } const minhaInstancia = new MinhaClasse();
O resultado da execução do código acima é:
Saída:
O Decorador de Classe Log foi chamado Classe: [class MinhaClasse] Uma instância de MinhaClasse foi criada
A função `logClass()` recebe um argumento único chamado `alvo` do tipo `Function`. O argumento `alvo` é do tipo `Function` porque ele recebe o construtor da classe que está sendo decorada.
Para usar `logClass()` como um decorador para a classe `MinhaClasse`, usamos `@logClass` antes da declaração da classe. O nome do decorador deve ser igual ao nome da função que será utilizada.
Ao criar uma instância de `MinhaClasse`, o comportamento definido pelo decorador é executado junto com o construtor da classe, como demonstrado na saída.
Atualmente, os decoradores são um recurso experimental no TypeScript. Portanto, para usá-los, você precisa ativar a opção `experimentalDecorators` no arquivo `tsconfig.json`.
Para gerar um arquivo `tsconfig.json` na pasta do projeto, execute o seguinte comando no terminal:
tsc --init
Abra o arquivo `tsconfig.json` e descomente a opção `experimentalDecorators`, conforme mostrado abaixo:
Além disso, certifique-se de que sua versão de destino do JavaScript seja pelo menos ES2015.
Importância dos Decoradores TypeScript
Um código de qualidade é reconhecido pela sua legibilidade, capacidade de reutilização e facilidade de manutenção. Código legível é aquele que é fácil de entender e interpretar, comunicando claramente a intenção do desenvolvedor.
Já o código reutilizável permite que componentes como funções e classes sejam usados em outras partes de um aplicativo, ou em um novo aplicativo, sem grandes mudanças. Código sustentável é aquele que pode ser facilmente alterado, atualizado e corrigido ao longo do seu ciclo de vida.
Os decoradores do TypeScript ajudam a alcançar a legibilidade, reutilização e manutenção do código. Em primeiro lugar, os decoradores permitem melhorar o comportamento do código através de uma sintaxe declarativa, mais fácil de ler. A lógica pode ser encapsulada em decoradores e utilizada em diferentes partes do seu código.
Isso facilita a leitura e o entendimento do código, com os decoradores comunicando claramente a intenção do desenvolvedor.
Os decoradores não são descartáveis, mas sim reutilizáveis. Um decorador pode ser criado e usado diversas vezes em diferentes áreas de um projeto. Isso evita a duplicação de código, o que aumenta a capacidade de reutilização do código.
Decoradores também aumentam a flexibilidade e modularidade do código, permitindo a separação de diferentes funcionalidades em componentes independentes. Em conjunto com a capacidade de escrever código legível e reutilizável, isso facilita a manutenção do seu código.
Tipos de Decoradores TypeScript
Como mencionado anteriormente, os decoradores TypeScript podem ser aplicados a classes, propriedades de classe, métodos de classe, acessadores de classe e parâmetros de método de classe. A partir dos elementos que podem ser decorados, surgem os diferentes tipos de decoradores TypeScript. Estes incluem:
#1. Decorador de Classe
Um decorador de classe é utilizado para observar, modificar ou substituir uma definição de classe. Ele é declarado antes da classe que está sendo decorada. O decorador é aplicado ao construtor da classe. Em tempo de execução, ele será chamado com o construtor da classe como argumento.
Um exemplo de decorador de classe utilizado para impedir que uma classe seja estendida é:
function congelado(alvo: Function) { Object.freeze(alvo); Object.freeze(alvo.prototype) } @congelado class Veiculo { rodas: number = 4; constructor() { console.log("Um veículo foi criado") } } class Carro extends Veiculo { constructor() { super(); console.log("Um carro foi criado"); } } console.log(Object.isFrozen(Veiculo));
Para impedir que uma classe seja estendida, utiliza-se a função `Object.freeze()` e passa-se a classe que se deseja congelar. O decorador adiciona essa funcionalidade a uma classe. Para verificar se a classe `Veiculo` está congelada, basta passar a classe para `isFrozen()`. A saída do código é:
true
#2. Decorador de Propriedade
Um decorador de propriedade é usado para decorar uma propriedade de classe e é declarado antes da propriedade. Decoradores de propriedades podem ser usados para alterar ou observar a definição de uma propriedade.
Em tempo de execução, o decorador recebe dois argumentos. O primeiro é a função construtora da classe caso o membro seja estático, ou o protótipo da classe caso seja um membro de instância. O segundo argumento é o nome do membro, ou seja, a propriedade que está sendo decorada.
No TypeScript, membros estáticos são precedidos pela palavra-chave `static`. Eles podem ser acessados sem a necessidade de instanciar a classe. Já os membros de instância não têm a palavra-chave `static` e só podem ser acessados após a criação de uma instância da classe.
Um exemplo de decorador de propriedade:
function decoradorDeRodas(alvo: any, nomeDaPropriedade: string) { console.log(nomeDaPropriedade.toUpperCase()) } class Veiculo { @decoradorDeRodas rodas: number = 4; constructor() { console.log("Um veículo foi criado") } }
A saída da execução do código é:
RODAS
#3. Decorador de Método
Um decorador de método, declarado antes de um método, é utilizado para observar, modificar ou substituir a definição de um método. Ele recebe três argumentos: a função construtora da classe (membro estático) ou o protótipo da classe (membro de instância), o nome do membro e um descritor de propriedade para o membro. O descritor de propriedade é um objeto que fornece informações sobre os atributos e o comportamento de uma propriedade.
Decoradores de método são úteis quando você precisa executar alguma ação antes ou depois da chamada de um método, ou registrar informações sobre o método. Eles também podem ser usados para informar que um método está obsoleto, indicando que ele pode ser removido em versões futuras.
Um exemplo de decorador de método:
const logDescontinuado = (alvo: any, nomeDoMetodo: string, descritor: PropertyDescriptor) => { console.log(`${nomeDoMetodo} foi descontinuado`); console.log(descritor); } class Veiculo { rodas: number = 4; constructor() { console.log("Um veículo foi criado") } @logDescontinuado reabastecer(): void { console.log("Seu veículo está sendo reabastecido"); } }
Saída:
reabastecer foi descontinuado { value: [Function: reabastecer], writable: true, enumerable: false, configurable: true }
#4. Decoradores de Accessors
No TypeScript, existem dois tipos de métodos acessores: `get` e `set`. Métodos acessores são utilizados para controlar o acesso às propriedades de uma classe. Decoradores de acessores são utilizados para decorar esses métodos e são declarados antes da declaração do acessor. Decoradores de acessores funcionam da mesma forma que decoradores de métodos.
Um exemplo de decoradores de acessores:
const logRodas = (alvo: any, nomeDoAcessor: string, descritor: PropertyDescriptor) => { console.log(`${nomeDoAcessor} usado para obter o número de rodas`); console.log(descritor); } class Veiculo { private rodas: number = 4; constructor() { console.log("Um veículo foi criado") } @logRodas get numRodas(): number { return this.rodas; } }
Saída:
numRodas usado para obter o número de rodas { get: [Function: get numRodas], set: undefined, enumerable: false, configurable: true }
É importante notar que decoradores não podem ser aplicados a vários acessores `get/set` com o mesmo nome. Por exemplo, no código acima, não seria possível usar o decorador `logRodas` em um *setter* chamado `set numRodas`.
#5. Decoradores de Parâmetros
Um decorador de parâmetro é utilizado para observar a declaração de um parâmetro em um método. Ele é declarado antes do parâmetro. Decoradores de parâmetros recebem três argumentos: a função construtora da classe (membro estático) ou o protótipo da classe (membro de instância), o nome do membro (o nome do parâmetro) e o índice ordinal do parâmetro na lista de parâmetros da função, onde o primeiro parâmetro tem índice 0.
Um exemplo de decorador de parâmetros:
const logPassageiro = (alvo: Object, nomeDaPropriedade: string, indiceDoParametro: number) => { console.log(`Decorador no parâmetro de ${nomeDaPropriedade} no índice ${indiceDoParametro}`); } class Veiculo { private rodas: number = 4; constructor() { console.log("Um veículo foi criado") } pegarPassageiro(local: string, numPassageiros: string, @logPassageiro motorista: string) { console.log(`${numPassageiros} pegos em ${local} pelo ${motorista}`) } deixarPassageiro(motorista: string, @logPassageiro local: string, numPassageiros: string) { console.log(`${numPassageiros} deixados em ${local} pelo ${motorista}`) } }
Saída:
Decorador no parâmetro de pegarPassageiro no índice 2 Decorador no parâmetro de deixarPassageiro no índice 1
Conclusão
Os decoradores TypeScript são uma ótima maneira de melhorar a legibilidade do código e ajudar a escrever código modular e reutilizável. É possível declarar decoradores uma vez e usá-los várias vezes, o que também contribui para a manutenção geral do código.
Apesar de ainda serem um recurso experimental, os decoradores são muito úteis e vale a pena explorá-los.
Leia também sobre como converter uma string para número no TypeScript.