Menu

Decorators em TypeScript: de método, classe, campo e accessor

Decorators são funções que envolvem ou substituem membros de classe com a sintaxe @. Veja os decorators padrão que o TypeScript suporta sem nenhuma flag (de classe, método, getter, campo e accessor), decorator factories, addInitializer e como eles diferem do experimentalDecorators legado usado por Angular e NestJS.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um decorator é uma função que você anexa a uma classe ou a um membro de classe com @name. Ele recebe o método original (ou a classe, ou o campo) mais um objeto de contexto que o descreve, e pode retornar um substituto. O TypeScript suporta os decorators padrão sem nenhuma flag do compilador.

@logged roda uma vez, quando a classe é definida, e substitui add pelo wrapper que retorna. Toda chamada passa pelo wrapper. Os parâmetros genéricos mantêm intactos o this, os argumentos e o tipo de retorno do método, então add continua recebendo dois números e retornando um número.

Como os decorators são compilados

Os decorators padrão vêm de uma proposta do TC39 para o JavaScript, e o TypeScript os implementa desde o TypeScript 5.0. A proposta ainda não faz parte do padrão JavaScript, e o Node 24 não entende a sintaxe @, então quando o target é ES2022 (como nestas páginas) o compilador reescreve cada classe decorada em JavaScript puro que chama funções auxiliares (__esDecorate e __runInitializers, geradas no topo do arquivo). A saída roda em qualquer lugar em que ES2022 roda.

Uma consequência: um arquivo com decorators não roda com o type stripping nativo do Node (node file.ts), que só remove tipos e deixa o @ no lugar. O Node para com SyntaxError: Invalid or unexpected token. Compile-o antes com tsc ou um bundler.

Tipos de decorator e suas assinaturas

Todo decorator padrão tem o formato (value, context) => replacement | void. O que é o value, e o que você pode retornar, depende do que está sendo decorado:

DecoravalueTipo do contextoRetorno
classea classeClassDecoratorContextuma classe substituta, ou nada
métodoo métodoClassMethodDecoratorContextum método substituto
getter / settero getter ou o setterClassGetterDecoratorContext / ClassSetterDecoratorContextum getter ou setter substituto
campoundefinedClassFieldDecoratorContextuma função que transforma o valor inicial
campo accessor{ get, set }ClassAccessorDecoratorContext{ get?, set?, init? }

Todo objeto de contexto tem kind, name e addInitializer. O contexto de um membro de classe também tem static, private e um objeto access para ler o membro a partir de uma instância. Decorators também funcionam em membros estáticos e #private.

Decorator factories

Para passar opções, escreva uma função que retorna um decorator e chame-a no local do @. Isso é uma decorator factory:

@retry(3) chama retry primeiro, e a função que ele retorna é o decorator de fato. Vários decorators se empilham: em @a @b method(), b é aplicado primeiro e a envolve o resultado.

Decorators de classe e addInitializer

Um decorator de classe recebe a própria classe. Ele pode retornar uma subclasse para substituí-la, ou não retornar nada e só registrá-la em algum lugar. context.addInitializer registra código para rodar em um momento específico: em um decorator de classe, logo depois que a classe é totalmente definida; em um decorator de método, quando cada instância é construída.

O decorator de classe fica acima da classe (ou depois do export). Sem o @bound, chamar loose() lançaria um erro, porque o this seria undefined.

Decorators de campo e de accessor

Um decorator de campo não consegue ver nem interceptar atribuições posteriores: o value dele é undefined, e tudo o que ele pode retornar é uma função que transforma o valor inicial do campo. Para interceptar leituras e escritas, declare o campo com a palavra-chave accessor, que o transforma em um par de getter e setter sobre um armazenamento privado, e decore isso:

O accessor faz parte da mesma proposta. Ele gera um getter e um setter de verdade sobre um campo #private, e é por isso que p.price = -5 passa pelo set do decorator.

Decorators padrão vs experimentalDecorators legado

Antes do TypeScript 5.0, os únicos decorators que o TypeScript tinha eram uma versão inicial da proposta, ativada com experimentalDecorators. Essa flag ainda existe e troca o compilador para o modelo legado, com assinaturas e semântica diferentes:

Padrão (sem flag)Legado (experimentalDecorators)
Assinatura(value, context)(target, propertyKey, descriptor)
Como altera um métodoretorna uma nova funçãoaltera descriptor.value
Decorators de parâmetronão suportados (TS1206)suportados
emitDecoratorMetadatanão suportadosuportado (informação de tipo em tempo de execução via reflect-metadata)
Decorators de accessor ({ get, set, init }), addInitializersimnão
Baseado ema proposta do TC39um rascunho mais antigo dela

Um decorator escrito para um modelo não passa na verificação de tipos no outro. Aqui está um decorator no estilo legado em um projeto sem a flag:

index.ts(11,5): error TS1241: Unable to resolve signature of method decorator when called as an expression.
  The runtime will invoke the decorator with 2 arguments, but the decorator expects 3.

A solução é reescrevê-lo na forma padrão (value, context), como no primeiro exemplo desta página, ou ligar o modelo legado no projeto inteiro:

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

Angular, NestJS e TypeORM ainda definem experimentalDecorators nas configurações que suas ferramentas geram e que suas docs pedem. NestJS e TypeORM também precisam do emitDecoratorMetadata, porque leem tipos em tempo de execução: o NestJS para injetar parâmetros de construtor, o TypeORM para mapear propriedades para colunas:

// Legacy model: needs experimentalDecorators (and emitDecoratorMetadata for DI).
@Injectable()
class UsersService {
    constructor(@Inject(DB) private db: Database) {}
}

Se você usa um desses frameworks, escreva os decorators do jeito legado e siga as docs dele. Para código novo sem um framework desses, use os decorators padrão.

Quando usar decorators

Decorators combinam com comportamentos transversais que, sem eles, se repetiriam em muitos métodos: log, medição de tempo, cache, novas tentativas, verificações de acesso, validação e registro de classes em um container ou roteador. Eles também escondem o fluxo de controle, já que quem lê precisa procurar o que o @retry faz antes de saber o que o método faz. Para um ou dois usos, uma função de ordem superior simples (const fetchData = retry(3, rawFetch)) é mais simples e funciona também fora de classes.

Perguntas frequentes

O que são decorators no TypeScript?

Um decorator é uma função aplicada a uma classe ou a um membro de classe com a sintaxe @name. Ele recebe o que está sendo decorado e um objeto de contexto, e pode retornar um substituto: um método envolvido, uma nova classe ou uma função que transforma o valor inicial de um campo. Usos comuns são log, validação, cache e registro de classes.

Preciso de experimentalDecorators para usar decorators no TypeScript?

Não. Desde o TypeScript 5.0, os decorators padrão (TC39) funcionam sem nenhuma flag. O experimentalDecorators troca o compilador para o modelo de decorators antigo, o legado, sobre o qual frameworks como Angular e NestJS são construídos. Os dois usam assinaturas de função diferentes e não são intercambiáveis.

Qual é a diferença entre os decorators padrão e o experimentalDecorators?

Decorators padrão recebem (value, context) e retornam um substituto. Decorators legados recebem (target, propertyKey, descriptor) e alteram o property descriptor. Só o modelo legado suporta decorators de parâmetro e emitDecoratorMetadata; só o modelo padrão tem decorators de accessor que retornam { get, set, init } e context.addInitializer.

O TypeScript suporta decorators de parâmetro?

Só com o experimentalDecorators ligado. No modo padrão, um decorator em um parâmetro gera o erro TS1206: Decorators are not valid here, porque a proposta do TC39 não inclui decorators de parâmetro. Frameworks de injeção de dependência que decoram parâmetros de construtor precisam, portanto, da flag legada.

Em que ordem vários decorators são aplicados?

As expressões dos decorators são avaliadas de cima para baixo, mas aplicadas de baixo para cima: em @a @b method(), b envolve o método primeiro e a envolve o resultado. Então a roda por fora quando o método é chamado.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR