Un décorateur est une fonction que vous attachez à une classe ou à un membre de classe avec @name. Il reçoit la méthode d'origine (ou la classe, ou le champ) plus un objet de contexte qui la décrit, et il peut renvoyer un remplaçant. TypeScript prend en charge les décorateurs standard sans aucune option du compilateur.
@logged s'exécute une fois, quand la classe est définie, et remplace add par l'enveloppe qu'il renvoie. Chaque appel passe par cette enveloppe. Les paramètres génériques préservent les types de this, des arguments et du retour de la méthode : add prend donc toujours deux nombres et renvoie un nombre.
Comment les décorateurs sont compilés
Les décorateurs standard viennent d'une proposition TC39 pour JavaScript, que TypeScript implémente depuis TypeScript 5.0. La proposition ne fait pas encore partie du standard JavaScript, et Node 24 ne sait pas analyser la syntaxe @ : quand la cible est ES2022 (comme sur ces pages), le compilateur réécrit donc chaque classe décorée en JavaScript ordinaire qui appelle des fonctions auxiliaires (__esDecorate et __runInitializers, émises en haut du fichier). La sortie tourne partout où tourne ES2022.
Une conséquence : un fichier contenant des décorateurs ne peut pas s'exécuter avec le type stripping intégré de Node (node file.ts), qui se contente de retirer les types et laisse le @ en place. Node s'arrête avec SyntaxError: Invalid or unexpected token. Compilez d'abord avec tsc ou un bundler.
Sortes de décorateurs et leurs signatures
Chaque décorateur standard a la forme (value, context) => replacement | void. Ce qu'est value, et ce que vous pouvez renvoyer, dépend de ce qui est décoré :
| Décore | value | Type de contexte | Retour |
|---|---|---|---|
| classe | la classe | ClassDecoratorContext | une classe de remplacement, ou rien |
| méthode | la méthode | ClassMethodDecoratorContext | une méthode de remplacement |
| getter / setter | le getter ou le setter | ClassGetterDecoratorContext / ClassSetterDecoratorContext | un getter ou un setter de remplacement |
| champ | undefined | ClassFieldDecoratorContext | une fonction qui transforme la valeur initiale |
champ accessor | { get, set } | ClassAccessorDecoratorContext | { get?, set?, init? } |
Chaque objet de contexte possède kind, name et addInitializer. Le contexte d'un membre de classe possède aussi static, private et un objet access pour lire le membre sur une instance. Les décorateurs fonctionnent aussi sur les membres statiques et #private.
Fabriques de décorateurs
Pour passer des options, écrivez une fonction qui renvoie un décorateur et appelez-la à l'endroit du @. C'est une fabrique de décorateurs :
@retry(3) appelle d'abord retry, et la fonction qu'il renvoie est le véritable décorateur. Plusieurs décorateurs s'empilent : dans @a @b method(), b est appliqué en premier et a enveloppe le résultat.
Décorateurs de classe et addInitializer
Un décorateur de classe reçoit la classe elle-même. Il peut renvoyer une sous-classe pour la remplacer, ou ne rien renvoyer et simplement l'enregistrer quelque part. context.addInitializer enregistre du code à exécuter à un moment précis : pour un décorateur de classe, juste après la définition complète de la classe ; pour un décorateur de méthode, à la construction de chaque instance.
Le décorateur de classe se place au-dessus de la classe (ou après export). Sans @bound, appeler loose() lèverait une exception, car this vaudrait undefined.
Décorateurs de champ et d'accessor
Un décorateur de champ ne peut ni voir ni intercepter les assignations ultérieures : sa value vaut undefined, et il ne peut renvoyer qu'une fonction qui transforme la valeur initiale du champ. Pour intercepter les lectures et les écritures, déclarez le champ avec le mot-clé accessor, qui le transforme en une paire getter/setter adossée à un stockage privé, et décorez-le :
accessor fait partie de la même proposition. Il émet un vrai getter et un vrai setter au-dessus d'un champ #private, c'est pourquoi p.price = -5 passe par le set du décorateur.
Décorateurs standard ou anciens experimentalDecorators
Avant TypeScript 5.0, les seuls décorateurs de TypeScript étaient une version préliminaire de la proposition, activée avec experimentalDecorators. Cette option existe toujours, et elle fait passer le compilateur à l'ancien modèle, avec des signatures et une sémantique différentes :
| Standard (sans option) | Ancien (experimentalDecorators) | |
|---|---|---|
| Signature | (value, context) | (target, propertyKey, descriptor) |
| Comment il modifie une méthode | renvoie une nouvelle fonction | modifie descriptor.value |
| Décorateurs de paramètres | non pris en charge (TS1206) | pris en charge |
emitDecoratorMetadata | non pris en charge | pris en charge (informations de type à l'exécution via reflect-metadata) |
Décorateurs accessor ({ get, set, init }), addInitializer | oui | non |
| Basé sur | la proposition TC39 | un brouillon plus ancien de celle-ci |
Un décorateur écrit pour un modèle ne passe pas la vérification des types dans l'autre. Voici un décorateur de l'ancien style dans un projet sans l'option :
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.
La correction consiste soit à le réécrire sous la forme standard (value, context), comme dans le premier exemple de cette page, soit à activer l'ancien modèle pour tout le projet :
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Angular, NestJS et TypeORM activent toujours experimentalDecorators dans les configurations générées par leurs outils et le demandent dans leur documentation. NestJS et TypeORM ont aussi besoin de emitDecoratorMetadata, car ils lisent les types à l'exécution : NestJS pour injecter les paramètres du constructeur, TypeORM pour faire correspondre les propriétés aux colonnes :
// Legacy model: needs experimentalDecorators (and emitDecoratorMetadata for DI).
@Injectable()
class UsersService {
constructor(@Inject(DB) private db: Database) {}
}
Si vous utilisez l'un de ces frameworks, écrivez les décorateurs à l'ancienne et suivez sa documentation. Pour du nouveau code sans ce type de framework, utilisez les décorateurs standard.
Quand utiliser les décorateurs
Les décorateurs conviennent aux comportements transversaux qui seraient sinon répétés dans de nombreuses méthodes : journalisation, mesure du temps, mise en cache, nouvelles tentatives, contrôles d'accès, validation, et enregistrement de classes auprès d'un conteneur ou d'un routeur. Ils masquent aussi le flux de contrôle, puisqu'un lecteur doit aller voir ce que fait @retry avant de savoir ce que fait la méthode. Pour un ou deux usages, une simple fonction d'ordre supérieur (const fetchData = retry(3, rawFetch)) est plus simple et fonctionne aussi en dehors des classes.
Questions fréquentes
Que sont les décorateurs en TypeScript ?
Un décorateur est une fonction appliquée à une classe ou à un membre de classe avec la syntaxe @name. Il reçoit l'élément décoré et un objet de contexte, et peut renvoyer un remplaçant : une méthode enveloppée, une nouvelle classe, ou une fonction qui transforme la valeur initiale d'un champ. Les usages courants sont la journalisation, la validation, la mise en cache et l'enregistrement de classes.
Faut-il experimentalDecorators pour utiliser les décorateurs en TypeScript ?
Non. Depuis TypeScript 5.0, les décorateurs standard (TC39) fonctionnent sans option. experimentalDecorators fait passer le compilateur à l'ancien modèle de décorateurs, sur lequel reposent des frameworks comme Angular et NestJS. Les deux utilisent des signatures de fonction différentes et ne sont pas interchangeables.
Quelle est la différence entre les décorateurs standard et experimentalDecorators ?
Les décorateurs standard reçoivent (value, context) et renvoient un remplaçant. Les anciens décorateurs reçoivent (target, propertyKey, descriptor) et modifient le descripteur de propriété. Seul l'ancien modèle prend en charge les décorateurs de paramètres et emitDecoratorMetadata ; seul le modèle standard propose les décorateurs accessor qui renvoient { get, set, init } et context.addInitializer.
TypeScript prend-il en charge les décorateurs de paramètres ?
Seulement avec experimentalDecorators activé. En mode standard, un décorateur sur un paramètre provoque l'erreur TS1206: Decorators are not valid here, car la proposition TC39 n'inclut pas les décorateurs de paramètres. Les frameworks d'injection de dépendances qui décorent les paramètres du constructeur ont donc besoin de l'ancienne option.
Dans quel ordre s'appliquent plusieurs décorateurs ?
Les expressions de décorateurs sont évaluées de haut en bas, mais appliquées de bas en haut : dans @a @b method(), b enveloppe d'abord la méthode et a enveloppe le résultat. a s'exécute donc à l'extérieur quand la méthode est appelée.