Décorateurs

Des fonctions attachées avec un signe @ à des classes et à leurs membres, qui étendent ou modifient leur comportement.

Renvoie
Pour une classe, une méthode et un accesseur : un membre qui remplace l’original, ou rien (pas de changement). Un décorateur de champ peut renvoyer une fonction qui transforme la valeur initiale.
Sur cette page

Exemple

#
TypeScript
function logged<This, Args extends unknown[], Result>(
  method: (this: This, ...args: Args) => Result,
  context: ClassMethodDecoratorContext<This>
) {
  const name = String(context.name)
  return function (this: This, ...args: Args): Result {
    console.log(`LOG: ${name}(${args.join(', ')})`)
    return method.apply(this, args)
  }
}

class ScoreBoard {
  private total = 0

  @logged
  add(points: number) {
    this.total += points
    return this.total
  }
}

const board = new ScoreBoard()
board.add(20)
console.log(board.add(15))
Résultat
LOG: add(20)
LOG: add(15)
35

Définition et utilisation

#

Un décorateur est une fonction ordinaire que vous attachez avec un signe @ à une classe ou à l’un de ses membres, par exemple @logged au-dessus d’une méthode. Il s’exécute une fois, au moment où la classe est définie, et reçoit le membre décoré ainsi qu’un objet context qui le décrit. S’il renvoie une nouvelle fonction, celle-ci remplace la méthode d’origine. Une logique écrite une seule fois, par exemple la journalisation, la mesure du temps ou la vérification des droits, peut alors être attachée à n’importe quelle méthode en une seule ligne.

Depuis la version 5.0, TypeScript prend en charge les décorateurs standards de la proposition TC39, sans aucune option supplémentaire. Vous pouvez les utiliser sur les classes, les méthodes, les getters et setters, les champs, ainsi que les champs avec le modificateur accessor. Ils ne fonctionnent pas sur les fonctions ordinaires ni sur les paramètres, où le compilateur signale « Decorators are not valid here ». Avec une target inférieure à ESNext, le compilateur transforme les décorateurs en JavaScript ordinaire à l’aide de fonctions utilitaires.

De nombreuses bibliothèques populaires, par exemple NestJS et TypeORM, utilisent une version plus ancienne et expérimentale des décorateurs, activée par l’option experimentalDecorators. Ces décorateurs reçoivent des arguments différents (l’objet cible, le nom et un descripteur de propriété), peuvent marquer des paramètres, et fonctionnent avec l’option emitDecoratorMetadata. Les deux versions sont incompatibles : un décorateur écrit pour l’une ne fonctionnera pas dans l’autre.

Syntaxe

#
Syntaxe
function decorator(value, context) {
  return replacement // optionnel
}

@decorator
class SomeClass {
  @decorator method() { … }
  @decorator field = 1
}

Arguments du décorateur

#
  • value

    dépend du membre

    Le membre décoré : une classe, une méthode, un getter ou un setter, et pour un champ accessor, un objet avec get et set. Pour un champ ordinaire, c’est undefined, car au moment où la classe est définie, le champ n’a pas encore de valeur.
  • context

    ClassMethodDecoratorContext et types apparentés

    Une description du membre : kind (le type, par exemple "method" ou "field"), name (le nom), static, private, ainsi que la méthode addInitializer(), qui enregistre du code à exécuter lors de l’initialisation.

Plus d’exemples

#
Un décorateur avec des arguments
TypeScript
function clamp(min: number, max: number) {
  return function (_value: undefined, _context: ClassFieldDecoratorContext<unknown, number>) {
    return (initial: number) => Math.min(max, Math.max(min, initial))
  }
}

class ReaderSettings {
  @clamp(12, 24) fontSize = 40
  @clamp(1, 3) columns = 0
}

console.log(new ReaderSettings())
Résultat
ReaderSettings { fontSize: 24, columns: 1 }
Un décorateur de classe
TypeScript
const registry: string[] = []

function register(_value: new (...args: any[]) => object, context: ClassDecoratorContext) {
  registry.push(String(context.name))
}

@register
class QuizTask {}

@register
class CodeTask {}

console.log(registry)
Résultat
['QuizTask', 'CodeTask']

Bonnes pratiques

#
  • Un décorateur avec des arguments, par exemple @clamp(12, 24), est une fonction qui renvoie le véritable décorateur. C’est ainsi que sont construits la plupart des décorateurs des bibliothèques.
  • Un décorateur de champ ne change que la valeur initiale. Pour contrôler chaque écriture ultérieure, marquez le champ avec le mot-clé accessor et utilisez un décorateur qui remplace son get et son set.
  • Vérifiez que tsconfig.json ne contient pas experimentalDecorators: true. Dans ce cas, ce sont les anciennes signatures qui s’appliquent, et les exemples de cette page ne compileront pas.

Entrées associées

#

Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.