Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Decorators no JavaScript
Lucas Santos
Lucas Santos

Posted on • Originally published atblog.lsantos.dev on

Decorators no JavaScript

Decorators são uma das mais antigaspropostas do JavaScript. Quantas vezes você já não ouviu que "O JavaScript vai ter decorators em breve"? Mas o que são esses decorators e o que eles vão mudar na nossa vida? Hoje ela está em estágio 3, o que significa que o tempo para ela ir ao ar reduz drásticamente, mas ainda sim não temos uma resposta concreta.

Se você não sabe como funciona o JavaScript, nesse vídeo eu explico um pouco mais sobre o processo de lançamento de novas funcionalidades do JavaScript, se você ainda não assistiu, eu recomendo fortemente para poder entender melhor como tudo funciona!

Decorators

Decorators são o nome curto para asdecorator functions , que é um padrão de projeto, inclusive. Eles são uma função (ou método) que modificam o comportamento de outra função passada, retornando uma nova função.

Essencialmente você pode implementar decorators em qualquer linguagem, afinal eles são um padrão de projetos. No JavaScript você poderiam implementar um decorator da seguinte forma:

const decorator = (fn) => {  return (...params) => {    console.log('antes da função')    const resultado = fn.call(this, ...params)    console.log('depois da função')    return resultado  }}const func = (nome) => console.log(`Olá ${nome}`)const decorada = decorator(func)decorada('Lucas')// antes da função// Olá Lucas// depois da função
Enter fullscreen modeExit fullscreen mode

Porém, algumas linguagens possuem uma sintaxe especial para chamar decorators, como Python e Java, por exemplo, veja como podemos criar um decorator em Python:

def decorator(fn):    def wrap():        print("antes da função")        fn()        print("depois da função")    return wrap@decoratordef sayHello():    print("hello!")sayHello()# antes da função# hello!# depois da função
Enter fullscreen modeExit fullscreen mode

Percebe que temos um@decorator? Essa é a sintaxe mais utilizada para chamarmos um decorator na função que vem logo em seguida.

A maioria das linguagens permite que decorators sejam aplicados em diversos locais, como classes, métodos, propriedades e etc. No JavaScript esse não foi sempre o caso, a versão anterior da proposta (que estava no estágio 2) dizia que os decorators só poderiam ser aplicados à classes e a nenhum outro tipo de objeto.

Com a nova proposta, os decorators podem ser aplicados nos seguintes tipos de objetos:

  • Classes (como já eram aplicados antes)
  • Propriedades de classes
  • Métodos de classes
  • Acessores de classes

Ou seja, ainda estamos focando na classe, mas não é mais somente na instância da classe, mas sim em tudo que vem dentro dela.

Usando decorators

Decorators são essencialmente funções, como vimos antes. Todas essas funções vão levar dois parâmetros:

  1. O valor que está sendo decorado, que é o elemento que aquele decorator está aplicado
  2. Um objeto de contexto, contendo informações sobre o valor decorado

Tenha em mente que o valor decorado é umareferência para o objeto original, ou seja, qualquer mudança nesse valor, vai interferir com o valor original.

O tipo declarado (tirado da proposta) é exatamente esse:

type Decorator = (value: Input, context: {  kind: string;  name: string | symbol;  access: {    get?(): unknown;    set?(value: unknown): void;  };  private?: boolean;  static?: boolean;  addInitializer?(initializer: () => void): void;}) => Output | void;
Enter fullscreen modeExit fullscreen mode

Nesse tipo,Input eOutput representam, respectivamente, o objeto que você está decorando e o retorno do decorator, que é uma função. Cada tipo de decorator pode retornar um tipo de função diferente e tem um tipo de input diferente, seja ele um decorator de classe, propriedade ou acessor.

O objeto de contexto também varia de acordo com o valor que você está decorando, então ele pode ou não pode conter alguns dos campos, por exemplo, o campoaccess só existe para acessores.

As demais propriedades tem valores bem fixos, por exemplo:

  • kind é o tipo do objeto que você está decorando, essa propriedade existe basicamente para verificar se você está usando o decorator corretamente e buscando as propriedades corretas. Os valores possíveis são:class,method,getter,setter,field eaccessor
  • name é o nome do objeto decorado, no caso de elementos privados vai ser a descrição (que é o próprio nome da propriedade)
  • access um objeto que contém duas possíveis chavesget eset que são funções usadas para acessar o valor decorado. É importante notar que esses valores são os valores finais que são passados para a instância do objeto, e não o valor que foi passado para o decorator
  • static indica se o valor é um elemento estático de uma classe, portanto só se aplica para elementos que podem ser estáticos
  • private indica se o elemento é privado, e tem a mesma regra dostatic
  • addInitializer é uma função extra que permite que você adicione uma lógica de inicialização do objeto decorado, todos os tipos possuem essa funcionalidade e ele operapor classe e nãopor instância , ou seja, ele só vai executar em objetos que não temkind === 'field'

Ordem de aplicação

Assim como todos os elementos que suportam vários tipos de uso, os decorators são aplicados em uma ordem.

Primeiro eles só são aplicados quando todos foram chamados. Depois disso, os decorators são aplicados da menor ordem para a maior ordem, isso significa que primeiramente todos os decorators de métodos e campos são chamados e aplicados e depois os decorators de classe são aplicados e, por fim, todos os decorators de campos estáticos são aplicados.

Além disso, não existem regras especiais quanto a qual tipo de função pode ser usada como um decorator, desde que ela siga a assinatura proposta, qualquer função pode ser aplicada como um decorator.

Tipos de decorators

Vamos agora passar um por um com os tipos de decorators que temos nessa proposta, começando com a maior ordem e descendo para as ordens menores.

Class methods

Decorators aplicados em métodos de classe como a seguir:

class foo {    @dec    metodo (arg) {}}
Enter fullscreen modeExit fullscreen mode

Esse tipo de decorator segue a seguinte tipagem:

type ClassMethodDecorator = (value: Function, context: {  kind: "method";  name: string | symbol;  access: { get(): unknown };  static: boolean;  private: boolean;  addInitializer(initializer: () => void): void;}) => Function | void;
Enter fullscreen modeExit fullscreen mode

Veja que okind vai ser sempremethod e o acessor vai ter somente o métodoget, uma vez que você não pode darset em um método.

O parâmetrovalue é o método que está sendo decorado, além disso o decorator pode ou não retornar um novo método que vai substituir o método que está sendo decorado, se ele não retornar nada então o método será executado normalmente.

Um exemplo clássico é o decorator de log que mostrei no início do artigo, podemos criar um novo decorator para realizar um log do que está sendo executado pelo método, executar o método em si e depois retornar o resultado:

function debug(value, { kind, name }) {  if (kind === "method") {    return function (...args) {      console.log(`começando ${name} com os argumentos ${args.join(", ")}`);      const ret = value.call(this, ...args);      console.log(`fim de ${name}`);      return ret;    };  }}class C {  @debug  m(arg) {}}new C().m(1);// começando m com os argumentos 1// fim de m
Enter fullscreen modeExit fullscreen mode

Veja que neste caso estamos retornando uma função que vai substituir o método na classe original (o protótipo vai ser substituído), se não retornássemos nada, somente o decorator seria executado.

Se quiséssemos fazer isso sem usar os decorators, podemos imaginar que temos a classe e estamos substituindo o métodom no protótipo diretamente pela chamada com o nosso decorator:

class C {    m(arg) {}}C.prototype.m = debug(C.prototype.m, { kind: 'method', name: 'm' }) ?? C.prototype.m
Enter fullscreen modeExit fullscreen mode

Acessores de classe

Os acessores de classes (comoget eset) podem ter duas assinaturas dependendo do tipo de acessor que estamos falando, paraget:

type ClassGetterDecorator = (value: Function, context: {  kind: "getter";  name: string | symbol;  access: { get(): unknown };  static: boolean;  private: boolean;  addInitializer(initializer: () => void): void;}) => Function | void;
Enter fullscreen modeExit fullscreen mode

E para oset a diferença é que okind vai sersetter e vamos ter umaccess com uma funçãoset:

type ClassSetterDecorator = (value: Function, context: {  kind: "setter";  name: string | symbol;  access: { set(value: unknown): void };  static: boolean;  private: boolean;  addInitializer(initializer: () => void): void;}) => Function | void;
Enter fullscreen modeExit fullscreen mode

O funcionamento é exatamente igual aos decorators de métodos, porém é importante notar que os decorators de acessores são aplicadosseparadamente para getters e setters ou seja:

class C {  @foo  get x() {    // ...  }  set x(val) {    // ...  }}
Enter fullscreen modeExit fullscreen mode

Nessa classe, o decorator está decorando apenas oget x() e não oset x(val). Eles são tão iguais que podemos reutilizar a mesma funçãodebug que tínhamos anteriormente, só precisamos tratar os novos tipos dekind:

function debug(value, { kind, name }) {  if (['method', 'getter', 'setter'].contains(kind)) {    return function (...args) {      console.log(`começando ${name} com os argumentos ${args.join(", ")}`);      const ret = value.call(this, ...args);      console.log(`fim de ${name}`);      return ret;    };  }}class C {  @debug  set x(arg) {}}new C().x = 1// começando x com os argumentos 1// fim de x
Enter fullscreen modeExit fullscreen mode

Da mesma forma, podemos aplicar essa funcionalidade sem o uso de decorators usandoObject.defineProperty:

class C {  set x(arg) {}}let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");set = debug(set, {  kind: "setter",  name: "x",  static: false,  private: false,}) ?? set;Object.defineProperty(C.prototype, "x", { set });
Enter fullscreen modeExit fullscreen mode

Propriedades de classe (class fields)

Esse tipo de decorator usa a tipagem completa:

type ClassFieldDecorator = (value: undefined, context: {  kind: "field";  name: string | symbol;  access: { get(): unknown, set(value: unknown): void };  static: boolean;  private: boolean;}) => (initialValue: unknown) => unknown | void;
Enter fullscreen modeExit fullscreen mode

Ele possui tanto os acessoresget quanto oset, além de possuir as propriedadesstatic eprivate, porém, diferente dos demais, ele não possui um métodoaddInitializer já que propriedades não podem ser inicializadas dessa forma.

Também, diferente dos demais tipos de decorators, como propriedades não tem um valor direto de input, portanto ovalue é sempreundefined ou seja, você não recebe a propriedade e nem uma referência dela, ao invés disso você pode retornar uma função que recebe o valor inicial e retorna um novo valor sempre que a propriedade é atribuída.

Para podermos usar a nossa função de debug nesses casos, vamos precisar de uma pequena modificação, já que não podemos retornar uma função nova mas sim um valor inicial.

function debug (_, {kind, name}) {    if (king === 'field') {        return function (initialValue) {             console.log(`inicializando variável ${name} com valor ${initialValue}`)            return initialValue        }    }}
Enter fullscreen modeExit fullscreen mode

E então podemos usar o nosso campo da seguinte maneira:

class C {    @debug x = 1}new C()// Inicializando variável x com valor 1
Enter fullscreen modeExit fullscreen mode

E podemos implementar esse mesmo comportamento usando uma chamada de inicialização na propriedade:

const inicializarX = debug(undefined, { kind: 'field', name: 'x' }) ?? (initialValue) => initialValueclass C {    x = inicializarX.call(this, 1)}
Enter fullscreen modeExit fullscreen mode

Um dos exemplos interessantes que a própria proposta apresenta é que, como a função de inicialização é chamada com a instância da classe comothis, então esse tipo de decorator pode ser utilizado para criar relações de inicialização, tipo registrar uma classe filha em uma classe pai como é mostrado no exemplo abaixo:

const CHILDREN = new WeakMap();function registerChild(parent, child) {  let children = CHILDREN.get(parent);  if (children === undefined) {    children = [];    CHILDREN.set(parent, children);  }  children.push(child);}function getChildren(parent) {  return CHILDREN.get(parent);}function register() {  return function(value) {    registerChild(this, value);    return value;  }}class Child {}class OtherChild {}class Parent {  @register child1 = new Child();  @register child2 = new OtherChild();}let parent = new Parent();getChildren(parent); // [Child, OtherChild]
Enter fullscreen modeExit fullscreen mode

Claro que você também pode usar uma lista de classes filhas interna da classe pai, por exemplo, para registrar injeção de dependências.

Classes

O último tipo de decorator é também um dos mais comuns, o decorator de classe. Ele segue uma versão simplificada da interface:

type ClassDecorator = (value: Function, context: {  kind: "class";  name: string | undefined;  addInitializer(initializer: () => void): void;}) => Function | void;
Enter fullscreen modeExit fullscreen mode

A grande diferença além dokind é que não temos métodos acessores e também não temos as propriedades para privada e estática.

O primeiro parâmetro vai ser sempre a classe que está sendo decorada e ele pode retornar um novo objeto do tipocallable, que é uma função, uma classe, um Proxy ou qualquer outra coisa que possa ser invocada.

Um exemplo, é estendermos o construtor de uma classe para que possamos incluir uma chamada para um console sempre que uma nova classe for invocada:

function debug (value, {kind, name}) {    if (kind === 'class') {        return class extends value {            constructor (...args) {                super(...args)                console.log(`construindo uma nova instancia de ${name} com os argumentos ${args.join(', ')}`)            }        }    }}
Enter fullscreen modeExit fullscreen mode

E usarmos na nossa classe dessa forma:

@debugclass C {}new C(1)// construindo uma nova instancia de C com os argumentos 1
Enter fullscreen modeExit fullscreen mode

Essencialmente podemos fazer o mesmo da seguinte forma sem decorators:

class C {}C = debug(C, {kind: 'class', name: 'C'}) ?? Cnew C(1)
Enter fullscreen modeExit fullscreen mode

Auto accessors

Juntamente com a proposta de decorators, esse documento também propõe um outro elemento da sintaxe chamadoauto accessors. Hoje podemos declarar acessores da seguinte forma:

class foo {    #privado = true    get getPrivado () { return this.#privado }    set setPrivado (val) { this.#privado = val }}
Enter fullscreen modeExit fullscreen mode

Assim vamos ter uma propriedadegetPrivado e umasetPrivado para poder acessar propriedades privadas dentro de classes, o que é bem útil quando temos que fazer algum tratamento de dados ou então setar algum tipo de informação que exija algum processamento prévio.

O que a proposta apresenta é a nova keywordaccessor, que vão fazer as seguintes operações:

  1. Criar uma propriedade privada de mesmo nome dentro da classe
  2. Criar um acessorget e um acessorset para essa propriedade com o mesmo nome

No final teremos uma sintaxe como essa:

class C {    acessor x = 1}const c = new C()c.x // 1c.x = 2c.x // 2
Enter fullscreen modeExit fullscreen mode

Isso é o mesmo do que fazermos:

class C {    #x = 1    get x() {        return this.#x    }    set (val) {        this.#x = val    }}
Enter fullscreen modeExit fullscreen mode

Um detalhe é que também podemos ter acessores privados:

class C {    accessor #x = 2}
Enter fullscreen modeExit fullscreen mode

Ao meu ver, a proposta apresenta osauto-accessors como uma forma de contornar o problema de que não podemos setar um decorator automaticamente para umget e umset, como expliquei antes, então teríamos que chamar duas vezes a mesma função para, essencialmente, a mesma variável.

Os auto-accessors usam uma versão um pouco diferente da interface:

type ClassAutoAccessorDecorator = (  value: {    get: () => unknown;    set(value: unknown) => void;  },  context: {    kind: "accessor";    name: string | symbol;    access: { get(): unknown, set(value: unknown): void };    static: boolean;    private: boolean;    addInitializer(initializer: () => void): void;  }) => {  get?: () => unknown;  set?: (value: unknown) => void;  init?: (initialValue: unknown) => unknown;} | void;
Enter fullscreen modeExit fullscreen mode

Como você pode ver, o valor que recebemos no primeiro parâmetro é um objeto com os dois acessores da propriedade. O objeto de contexto recebe umkind comoaccessor, a propriedadeaccess com ambas as funçõesget eset e as demais propriedades que estamos vendo nas outras interfaces.

A questão é que, para o primeiro parâmetro, vamos receber o objeto com os dois acessores queestão definidos no protótipo da classe , ou seja, é o próprio objeto de acesso que a a classe vai ter. No caso de termos um acessor estático, vamos receber a própria classe.

Esse objeto existe para que o decorator possa criar umwrap em volta deles e retornar um novoget e/ou um novoset, essencialmente criando um proxy que intercepta as chamadas para qualquer um desses acessores. O que não é possível com propriedades de classe normalmente.

Adicionalmente, quando retornamos o objeto com as propriedades, também podemos retornar uma funçãoinit que é uma função de inicialização que pode ser utilizada para mudar o valor inicial da variável privada que está setada na classe. Se você retornar o objeto sem qualquer um dos valores, sejaget,set ouinit o valor original do acessor vai ser usado.

Criando um exemplo com nosso decorator de debug, podemos fazer uma extensão para ele trabalhar com os auto-accessors:

function debug (target, {kind, name}) {  if (kind === 'accessor') {    const {get, set} = target    return {      get() {        console.log(`get ${name}`)        return get.call(this)      },      set(val) {        console.log(`set ${name} para ${val}`)        return set.call(this, val)      },      init (initialValue) {        console.log(`iniciando ${name} com o valor ${initialValue}`)        return initialValue      }    }  }}
Enter fullscreen modeExit fullscreen mode

Como você pode perceber, auto-accessors são um pouco mais longos de trabalhar porque você precisa retornar um objeto de funções, mas não é nada muito além do que já fizemos aqui na maioria dos outros casos.

Depois podemos usá-los da seguinte forma:

class C {  @debug accessor x = false}const c = new C()// iniciando x com o valor falsec.x// get xc.x = true// set x para truec.x // get x
Enter fullscreen modeExit fullscreen mode

Se quisermos fazer a mesma coisa sem os decorators, vamos usar uma mistura do que temos nas propriedades e nos acessores que já fizemos antes:

class C {  #x = inicializarX.call(this, 1);  get x() {    return this.#x;  }  set x(val) {    this.#x = val;  }}let { get: oldGet, set: oldSet } = Object.getOwnPropertyDescriptor(C.prototype, "x");let {  get: newGet = oldGet,  set: newSet = oldSet,  init: initializeX = (initialValue) => initialValue} = logged(  { get: oldGet, set: oldSet },  {    kind: "accessor",    name: "x",    static: false,    private: false,  }) ?? {};Object.defineProperty(C.prototype, "x", { get: newGet, set: newSet });
Enter fullscreen modeExit fullscreen mode

addInitializer e inicialização de contexto

O métodoaddInitializer que vimos em algumas das interfaces no objeto de contexto de todos os decorators, exceto o de classe, é um método que pode ser chamado para associar uma função de inicialização com a classe ou o elemento que estamos decorando.

Esse método pode ser usado para rodar qualquer código depois que o valorjá foi setado permitindo que você possa finalizar a inicialização desse valor. Porém, a ordem de execução desses inicializadores depende do decorator que estamos usando:

  • Paraclasses , os inicializadores rodamdepois que a classe foi completamente definida, depois de todas as propriedades estáticas serem atribuídas
  • Para elementos de classe (class elements), os inicializadores rodamdurante a construção, masantes da inicialização das propriedades da classe
  • Para elementosestáticos , os inicializadores rodam tambémdurante a inicialização da classe,antes dos campos estáticos serem definidos, masdepois que todos os elementos de classe foram definidos

Alguns exemplos que a proposta apresenta.

@customElement

Podemos usar oaddInitializer para poder decorar uma classe que vai registrar um novo webComponent no browser:

function customElement (name) {    return (value, { addInitializer }) => {        addInitializer(function() {            customElements.define(name, this)        })    }}@customElement('elemento')class Elemento extends HTMLElement {    static get observedAttributes() {        return ['attr', 'att']    }}
Enter fullscreen modeExit fullscreen mode

Neste exemplo, perceba que podemos "decorar" um decorator fazendo umwrap com uma outra função para que possamos passar parâmetros para ele, nesse caso estamos querendo passar a o nome do elemento para o decorator, então podemos criar uma função que recebe o nome e retorna uma outra função com a mesma assinatura do decorator.

@bound

Um decorator que é aplicado em um método de uma classe para poder modificar o seuthis para othis daquela classe:

function bound (value, {name, addInitializer}) {    addInitializer(function () {        this[name] = this[name].bind(this)    })}class C {    message = 'oi!'    @bound    m() {        console.log(this.message)    }}const {m} = new C()m() // oi!
Enter fullscreen modeExit fullscreen mode

Perceba que, em ambos os casos, estamos usandofunction() dentro deaddInitializer, isto porque queremosmanter othis daquele escopo como sendo o escopo do decorator, isso é mais evidente nesse exemplo, mas também vale para o@customElement

Acessores de contexto

Um objeto que não utilizamos aqui foi o objetoaccess que vem de dentro dos contextos dos decorators.

Um exemplo muito útil é a criação de umcontâiner de injeção de dependências. Que é uma ferramenta muito útil para poder criar automaticamente instâncias de classes dependentes para classes que levam essas dependências, dessa forma você não precisa passar todas as dependências como parâmetros.

Isso já é uma realidade com a bibliotecaTSyringe feita pela Microsoft para demonstrar o poder dos decorators no TypeScript.

Essencialmente o que precisamos fazer é ter uma lista global de classes e suas dependências:

const INJETAVEIS = new WeakMap()function initContainer() {    const injecoes = []    function injetavel (Class) {        INJETAVEIS.set(Class, injecoes)    }    function injetar (chave) {        return function aplicarDependencia (alvo, contexto) {            injecoes.push({ chave, set: context.access.set })        }    }    return { injetavel, injetar }}
Enter fullscreen modeExit fullscreen mode

Essa função vai inicializar a nossa lista global de dependências para uma determinada classe, então o que precisamos fazer é anotar a classe que queremos automatizar com@injectable e as dependências dessa classe com@inject. Mas antes precisamos de um container que vai ser a nossa instância global que vai ler dessa lista:

class Container {    registro = new Map()    registrar (nome, valor) {        this.registro.set(nome, valor)    }    buscar (nome) {        return this.registry.get(nome)    }    criar (Classe) {        const instancia = new Classe()        for (const { chave, set } of INJETAVEIS.get(Classe) || []) {            set.call(instancia, this.buscar(chave))        }        return instancia    }}
Enter fullscreen modeExit fullscreen mode

Aqui o que estamos fazendo é criando um container que vai registrar as dependências globais, ou seja, todas as classes que instanciamos uma vez, esse registro vai ter o nome que quisermos dar e também a instância da classe que criamos.

Quando definirmos uma nova classe através do container usandocriar, vamos passar o construtor da classe que queremos criar, depois, vamos buscar todas as classes injetáveis que batem com essa descrição na nossa lista global e vamos chamar o métodoset para setar a uma nova propriedade na classe.

Quando chamamosset.call(instancia, this.buscar(chave)) estamos dizendo que queremos que a propriedade anotada chame seu acessorset com othis definido para a nova instância da classe que criamos, com o valor sendo a classe dependente que já instanciamos anteriormente.

Vamos dar um exemplo:

class Store {}const { injetavel, injetar } = initContainer()// A classe C é injetável e pode receber dependências externas@injetavelclass C {    // Essa propriedade é a instancia guardada na chave    // nomeDaclasse que registramos no container    @injetar('nomeDaClasse') store}const container = new Container()const store = new Store()// Registrando a Store no container como uma dependênciacontainer.register('nomeDaclasse', store)const c = container.create(C)c.store === store // true
Enter fullscreen modeExit fullscreen mode

Veja que estamos usandocontainer.create(C) para criar uma nova classe com as dependências já injetadas, mas isso não é totalmente necessário, como você pode vernessa documentação do TSyringe e como já mostrei antes, podemos usar os decorators para substituir completamente o construtor da classe e rodar essa lógica automaticamente para todas as dependências de uma mesma classe.

Testando você mesmo

Se você quiser rodar qualquer um dos códigos que eu coloquei por aqui, mesmo antes da proposta estar completamente publicada e disponível, isso é possível através de transpiladores como obabel.

Para isso, crie uma nova pasta em qualquer lugar e rodenpm init -y (lembrando que você precisa ter o Node e o NPM instalados), isso vai criar um novo arquivopackage.json, depois execute o comandonpm i -D @babel/cli @babel/core @babel/plugin-proposal-decorators @babel/preset-env.

Abra o arquivopackage.json e, na sessãoscripts, adicione um novo scripttranspile:

{    "scripts": {        "transpile": "babel src -d dist"    }}
Enter fullscreen modeExit fullscreen mode

Esse script vai pegar qualquer código.js dentro da pastasrc e vai transpilar para um novo arquivo na pastadist.

Agora crie um novo arquivo chamadobabel.config.json com esse conteúdo:

{  "presets": [    [      "@babel/preset-env",      {        "targets": {          "node": "current"        }      }    ]  ],  "plugins": [    [      "@babel/plugin-proposal-decorators",      {        "version": "2022-03"      }    ]  ]}
Enter fullscreen modeExit fullscreen mode

Escreva um arquivo de teste com qualquer um dos exemplos, ou então crie seu próprio, como esse:

@annotationclass MyClass {  @property accessor bool = false}function annotation(...params) {  console.log(params)}function property(target, name) {  console.log(target, name)  return {    get() {      console.log('get')      return target.get.call(this)    },    set(val) {      console.log('set', val)      return target.set.call(this, val)    }  }}function debug(target, { kind, name }) {  if (kind === 'accessor') {    const { get, set } = target    return {      get() {        console.log(`get ${name}`)        return get.call(this)      },      set(val) {        console.log(`set ${name} para ${val}`)        return set.call(this, val)      },      init(initialValue) {        console.log(`iniciando ${name} com o valor ${initialValue}`)        return initialValue      }    }  }}const a = new MyClass()console.log(a.bool)a.bool = trueconsole.log(a.bool)
Enter fullscreen modeExit fullscreen mode

Rodenpm run transpile e depoisnode dist/<arquivo>.js e veja a mágica acontecer!

Conclusão

Os decorators são um padrão de projeto incrível e possuem um potencial imenso para serem uma das funcionalidades mais interessantes da linguagem e permitirem que façamos muito mais coisas de forma muito simples.

Eu, particularmente, vejo uma grande adoção por ferramentas de monitoramento como NewRelic, NSolid e Datadog para Node.js e até mesmo o JavaScript no browser!

Comenta ai embaixo o que você achou dessa proposta e me marcalá no Twitter pra eu saber sua opinião!

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Brazilian developer since 2011. Microsoft MVP, Docker Captain, and Google Dev Expert. OpenJS foundation member and Node.js contributor. Loves JavaScript and TypeScript.
  • Location
    Stockholm - Sweden
  • Education
    Bachelor of Science and Technology
  • Pronouns
    He/him
  • Work
    Founding Engineer @ Openvolt | OSS Contributor | Professional Trainer
  • Joined

More fromLucas Santos

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp