Como criar Decorators em Typescript

Thumb para o post "Decorators em typescript"

Seja bem-vindo ao fascinante mundo dos decorators em JavaScript e TypeScript! 🚀 Neste post, vamos explorar o poder dessas ferramentas incríveis, desde adicionar funcionalidades extras a classes até personalizar o comportamento de métodos. Vamos mergulhar juntos nesse universo de possibilidades e desvendar como essas pequenas anotações podem transformar a maneira como estruturamos e organizamos nosso código. Vamos lá!

Decorator para Classe

O “Class Decorator” é constituído apenas de uma função que herda o constructor da classe alvo, e pode ou não retornar uma nova classe estendendo o constructor recebido como parâmetro.

Exemplo:

Nos dois exemplos abaixo, mostro como adicionar um novo método a classe utilizando a criação de uma nova classe herdando a original, ou com a manipulação usando prototype.

function meuDecorator<T extends {new(...args: any[]): {}}>(construtor: T) {
return class extends construtor {
meuMetodoAdicional() {
console.log("Este é um método adicional adicionado pelo decorator");
}
}
}
function meuOutroDecorator<T extends {new(...args: any[]): {}}>(construtor: T) {
constructor.prototype.meuOutroMetodoAdicional = function() {
console.log("Este é um outro método adicional adicionado pelo decorator");
}
}

Como utilizar:

Para utilizar um decorator no Typescript, precisamos somente “chamar” o decorator logo antes do nome da classe, utilizando o padrão @NomeDoDecorator. Este método é chamado de “Suggar decorator”, implementado pelo Typescript.

@meuDecorator
@meuOutroDecorator
class MinhaClasse {
constructor(private valor: string) {}
meuMetodo() {
console.log(`Valor: ${this.valor}`);
}
}

Decorator para Método

Como o Class Decorator, este é chamado exatamente antes do método, também sendo utilizado o padrão @NomeDoDecorator

Este decorator recebe 3 parâmetros em sua função de retorno, sendo eles:

  1. target – Representação da classe que contém o método Pode ser usado para acessar ou alterar a classe e/ou seus membros.
  2. propertyKey – Nome do método que esta sendo utilizado
  3. descriptor – Objeto com a descrição do método, utilizado para alterar o comportamento do método e/ou seus metadados.

Exemplo:

Neste exemplo, criamos um decorator para validação de Método HTTP, para limitar uma requisição em somente um método.

// Limitador de métodos para request
import { Request, Response } from "express";
export function AllowedMethod(method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE") {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (req: Request, res: Response): Promise<void | Response> {
if (req.method !== method) {
return res.status(405).json({ ok: false, message: "Method not allowed" });
}
return originalMethod.apply(this, [req, res]);
};
return descriptor;
};
}

No exemplo, modificamos o comportamento do método, para ser feita uma validação de Método HTTP antes de executar o código original.

Ao verificar a função decorada, definimos uma constante com uma cópia do método original, e após isso modificamos o valor com uma nova função que retornará uma execução do original.

Neste exemplo somente fazemos uma verificação, porém como pode ser visto, temos acesso a todas as informações do método original, sendo possível fazer uma variedade de modificações em seu comportamento, incluindo validação e/ou modificação de seus parâmetros.

Uso do Method decorator:

No código abaixo, utilizamos o decorator logo antes do método, podendo passar parâmetros para o mesmo.

Na primeira classe, inserimos o decorator no método de execução para limitar a requisição para somente métodos POST, e no segundo, para métodos GET.

class Login {
constructor() {}
@AllowedMethod("POST")
async exec(req: Request, res: Response) {
try {
const { username, password } = req.body;
// handle login
return res.status(200).json({ ok: true, user: {} });
} catch (error) {
console.log(`Error: `, error);
}
}
}
class GetUser {
constructor() {}
@AllowedMethod("GET")
async exec(req: Request, res: Response) {
try {
const { id } = req.body;
// handle get user
return res.status(200).json({ ok: true, user: {} });
} catch (error) {
console.log(`Error: `, error);
}
}
}

Decorator puro para funções

Os exemplos acima, o decorator usado é uma feature experimental do Typescript, e somente funcionam em Classes e seus derivados, porém, é possível criar decorators para funções normais de um modo um pouco diferente.

Importante saber que este método não pode ser usado com o padrão do typescript @NomeDaFuncao, porém segue um padrão um pouco parecido com o “method decorator” onde também pode ser passado parâmetros.

Este tipo de decorator pode ter vários nomes, como, “pure decorator” ou “simple decorator” e diferente do “suggar decorator” implementado pelo Typescript, este é feito para funções nativas ou arrow functions. Parar criar um decorator puro utilizamos a estratégia de “high order function”, enviando a função a ser executada como parâmetro, assim, podemos executar o decorator antes de chamar a função callback.

// Exemplo de decorator puro para funções (controle de acesso)
const user = {
name: "John Covv",
email: "contato@johncovv.com",
roles: ['user']
}
function guard(allowedRoles: Array<'admin' | 'user'>, callback: Function) {
if (!user || !user.roles || !user.roles.some((role) => allowedRoles.includes(role as any))) {
throw new Error('acesso negado');
}
return callback();
}
guard(['user'], () => console.log('acesso concedido'));
//esperado: log "acesso concedido"
guard(['admin'], () => console.log('acesso concedido'));
//esperado: disparar o erro "acesso negado"

No exemplo acima criamos o decorator “guard” para gerenciamento de rotas, como primeiro parâmetro devemos definir os cargos que tem acesso a request, e em seguida definimos a função callback que deve ser executada caso o usuário tenha o acesso correto.

🚀✨

É isso aí, chegamos ao final! Espero que essas dicas de decorators tenham feito seu código brilhar um pouquinho mais.

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será divulgado.


*


Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.