Introduction
Les décorateurs JavaScript sont encore à un stade expérimental, appelé "brouillon" ou "étape 2", ce qui signifie qu'il pourrait se passer un certain temps avant qu'ils ne soient intégrés dans la norme JavaScript. Cependant, TypeScript les prend en charge depuis un certain temps, mais seulement de manière expérimentale. Les décorateurs sont devenus très populaires dans des frameworks tels que Angular et Vue, où ils sont utilisés pour l'injection de dépendances et l'ajout de fonctions à une classe.
Comme les décorateurs ne sont pas encore standardisés, il est important de noter que leur implémentation et leurs normes dans JavaScript et TypeScript pourraient changer à tout moment. Cela signifie que le code de décorateur que nous écrivons aujourd'hui pourrait nécessiter de nombreuses modifications si les spécifications venaient à changer. Ce chapitre a pour but de présenter les décorateurs et de les comprendre, surtout pour ceux qui les utilisent beaucoup dans les frameworks.
Les décorateurs sont une fonctionnalité du compilateur TypeScript et sont pris en charge dans ES5 et les versions ultérieures. Pour les utiliser, nous devons activer une option de compilation dans le fichier tsconfig.json. Cette option s'appelle "experimentalDecorators" et doit être définie sur "true", comme suit :
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
En ajoutant cette option à notre fichier de configuration, nous permettons l'utilisation des décorateurs dans notre code TypeScript. Nous pouvons ensuite commencer à expérimenter avec les décorateurs et à ajouter des fonctionnalités à nos classes et objets.
Syntaxe
Un décorateur est une fonction qui est appelée avec un ensemble spécifique de paramètres. Ces paramètres sont automatiquement remplis par le runtime JavaScript et contiennent des informations sur la classe, la méthode ou la propriété à laquelle le décorateur a été appliqué. Le nombre et les types de ces paramètres déterminent où un décorateur peut être appliqué. Pour illustrer cette syntaxe, prenons l'exemple d'un décorateur de classe :
function simpleDecorator(constructor: Function) {
console.log('simpleDecorator called');
}
Cette fonction peut être utilisée comme décorateur de classe et appliquée à une définition de classe, comme suit :
@simpleDecorator
class ClassWithSimpleDecorator {}
L'exécution de ce code produira la sortie suivante :
simpleDecorator called
Dans cet exemple, nous n'avons pas encore créé d'instance de la classe nommée ClassWithSimpleDecorator. Tout ce que nous avons fait est de spécifier sa définition, d'y ajouter un décorateur et celui-ci a été automatiquement appelé par l'environnement d'exécution JavaScript. Cela montre que les décorateurs sont appliqués lorsqu'une classe est définie.
Prouvons cette théorie en créant quelques instances de cette classe :
let instance_1 = new ClassWithSimpleDecorator();
let instance_2 = new ClassWithSimpleDecorator();
console.log(`instance_1 : ${JSON.stringify(instance_1)}`);
console.log(`instance_2 : ${JSON.stringify(instance_2)}`);
résultat :
simpleDecorator called
instance_1 : {}
instance_2 : {}
Le code ci-dessus montre comment les décorateurs peuvent être utilisés pour ajouter des fonctionnalités supplémentaires à une classe en TypeScript. Lorsque nous appliquons un décorateur à une classe, la fonction du décorateur est appelée avec la classe en tant que paramètre.
Dans cet exemple, nous avons utilisé un décorateur simple appelé "simpleDecorator" pour ajouter une fonctionnalité à notre classe "ClassWithSimpleDecorator". Nous avons créé deux instances de cette classe, mais la fonction du décorateur n'a été appelée qu'une seule fois.
Il est également possible d'appliquer plusieurs décorateurs sur une même classe. Dans l'exemple ci-dessus, nous avons ajouté un deuxième décorateur appelé "secondDecorator" à notre classe "ClassWithMultipleDecorators". Les deux décorateurs ont alors été appelés, mais dans l'ordre inverse de leur apparition dans le code. Ainsi, le second décorateur a été appelé en premier, suivi du premier décorateur.
function secondDecorator(constructor: Function) {
console.log(`secondDecorator called`);
}
@simpleDecorator
@secondDecorator
class ClassWithMultipleDecorators {
}
Résultat:
secondDecorator called
simpleDecorator called
Il est important de noter que l'ordre dans lequel les décorateurs sont appliqués peut avoir un impact sur le comportement de la classe. Par conséquent, il est important de comprendre comment les décorateurs fonctionnent et de les utiliser avec prudence.
Les types
Les décorateurs en TypeScript peuvent être appliqués à différentes parties du code, selon le type de décorateur utilisé. Il existe quatre types de décorateurs : les décorateurs de classe, les décorateurs de propriétés, les décorateurs de méthode et les décorateurs de paramètres.
Les décorateurs de classe peuvent être appliqués à une définition de classe. Les décorateurs de propriétés peuvent être appliqués à une propriété à l'intérieur d'une classe. Les décorateurs de méthode peuvent être appliqués à une méthode sur une classe. Enfin, les décorateurs de paramètres peuvent être appliqués à un paramètre d'une méthode à l'intérieur d'une classe.
Pour illustrer ces différents types de décorateurs, prenons l'exemple suivant :
function classDecorator(
constructor: Function) {}
function propertyDecorator(
target: any,
propertyKey: string) {}
function methodDecorator(
target: any,
methodName: string,
descriptor?: PropertyDescriptor) {}
function parameterDecorator(
target: any,
methodName: string,
parameterIndex: number) {}
-
La première fonction, "classDecorator", peut être utilisée comme décorateur de classe. Elle a un seul paramètre nommé "constructor" de type "Function".
-
La deuxième fonction, "propertyDecorator", peut être utilisée comme décorateur de propriétés. Elle a deux paramètres : le premier paramètre est nommé "target" et est de type "any", et le deuxième paramètre est nommé "propertyKey" et est de type "string".
-
La troisième fonction, "methodDecorator", peut être utilisée comme décorateur de méthode. Elle a trois paramètres : le premier paramètre, nommé "target", est de type "any", et le deuxième paramètre est nommé "methodName" et est de type "string". Le troisième paramètre est un paramètre facultatif nommé "descriptor" et est de type "PropertyDescriptor".
-
La quatrième fonction, "parameterDecorator", peut être utilisée comme décorateur de paramètres. Elle a également trois paramètres : le premier paramètre est nommé "target" et est de type "any". Le deuxième paramètre est nommé "methodName" et est de type "string". Le troisième paramètre est nommé "parameterIndex" et est de type "number".
@classDecorator
class ClassWithAllTypesOfDecorators {
@propertyDecorator
id: number = 1;
@methodDecorator
print() { }
setId(@parameterDecorator id: number) { }
}
En effet, ce qui est important à retenir concernant les décorateurs en TypeScript, c'est que le nombre de paramètres et leurs types déterminent s'ils peuvent être utilisés comme décorateurs de classe, de propriété, de méthode ou de paramètre.
Lorsqu'un décorateur est appliqué à une classe, une propriété, une méthode ou un paramètre, il sera appelé par le runtime JavaScript au moment de l'exécution. Le runtime JavaScript fournira automatiquement les paramètres appropriés en fonction du type de décorateur appliqué.
Il est également important de noter que les décorateurs ne sont pas limités à un seul type de paramètre. Un décorateur peut être utilisé pour décorer à la fois une classe, une propriété, une méthode et un paramètre, en fonction de la manière dont il est défini. Cela permet une grande flexibilité dans la manière dont les décorateurs peuvent être utilisés pour ajouter des fonctionnalités à un code TypeScript.
Decorator factories
Il peut arriver que nous ayons besoin de définir un décorateur qui a des paramètres. Pour y parvenir, nous devons utiliser ce que l'on appelle une "factory function" de décorateur. Une factory function de décorateur est créée en enveloppant la fonction de décorateur elle-même dans une autre fonction.
Dans l'exemple suivant, nous définissons une fonction factory de décorateur qui prend un paramètre nommé "name". La fonction factory renvoie ensuite une fonction décoratrice anonyme qui enregistre un message dans la console qui inclut le paramètre "name".
function decoratorFactory(name: string) {
return (constructor: Function) => {
console.log(`decorator function called with : ${name}`);
};
}
Il peut arriver que nous ayons besoin de définir un décorateur qui a des paramètres. Pour y parvenir, nous devons utiliser ce que l'on appelle une "factory function" de décorateur. Une factory function de décorateur est créée en enveloppant la fonction de décorateur elle-même dans une autre fonction.
Dans l'exemple suivant, nous définissons une fonction factory de décorateur qui prend un paramètre nommé "name". La fonction factory renvoie ensuite une fonction décoratrice anonyme qui enregistre un message dans la console qui inclut le paramètre "name".
typescript
Copy code
function decoratorFactory(name: string) {
return (constructor: Function) => {
console.log(decorator function called with : ${name});
};
}
Nous pouvons maintenant utiliser cette fonction factory de décorateur pour décorer notre classe "ClassWithDecoratorFactory" comme suit :
@decoratorFactory('testName')
class ClassWithDecoratorFactory {
}
Le résultat sera un message enregistré dans la console qui inclut le paramètre "name" que nous avons transmis à la fonction factory de décorateur.
Il est important de noter que les fonctions factory de décorateur doivent retourner une fonction qui a le bon nombre et les bons types de paramètres en fonction du type de décorateur qu'ils sont. De plus, les paramètres définis pour la fonction factory de décorateur peuvent être utilisés n'importe où dans la définition de la fonction, y compris dans la fonction décoratrice anonyme elle-même.
Décorateur de classe
Les décorateurs de classe sont un concept clé en programmation orientée objet qui permettent de modifier le comportement des classes et de leurs instances. Les décorateurs sont des fonctions qui prennent la classe en tant que paramètre et qui peuvent ajouter des fonctionnalités supplémentaires à la classe.
Un exemple courant de décorateur de classe est la fonction @classConstructorDec. Cette fonction est appelée sur la classe ClassWithConstructor, qui est ensuite utilisée pour créer une instance de la classe. Le code ressemble à ceci :
function classConstructorDec(constructor: Function) {
console.log(`constructor : ${constructor}`);
}
@classConstructorDec
class ClassWithConstructor {
constructor(id: number) { }
}
Dans cet exemple, la fonction @classConstructorDec prend le constructeur de la classe ClassWithConstructor en tant que paramètre et l'affiche dans la console. Le constructeur de la classe est simplement une fonction qui prend un paramètre "id" et ne fait rien d'autre. Cependant, la fonction @classConstructorDec peut être utilisée pour ajouter des propriétés ou des méthodes supplémentaires à la classe.
Un autre exemple de décorateur de classe montre comment ajouter une propriété à l'objet prototype de la classe. Le code ressemble à ceci :
function classConstructorDec(constructor: Function) {
console.log(`constructor : ${constructor}`);
constructor.prototype.testProperty = "testProperty_value";
}
Dans cet exemple, la fonction @classConstructorDec ajoute une propriété "testProperty" à l'objet prototype de la classe. Cette propriété a la valeur "testProperty_value" et est automatiquement disponible pour toutes les instances de la classe.
Le dernier exemple de code montre comment accéder à la propriété "testProperty" d'une instance de la classe ClassWithConstructor. Le code ressemble à ceci :
let classInstance = new ClassWithConstructor(1);
console.log(`classInstance.testProperty =
${(<any>classInstance).testProperty}`);
Dans cet exemple, une instance de la classe ClassWithConstructor est créée en utilisant le constructeur de la classe. Ensuite, la propriété "testProperty" est accédée en utilisant la syntaxe pointée. Comme la propriété a été ajoutée à l'objet prototype de la classe, elle est automatiquement disponible pour toutes les instances de la classe.
Décorateur de propriétés
function propertyDec(target: any, propertyName: string) {
console.log(`target : ${target}`);
console.log(`target.constructor : ${target.constructor}`);
console.log(`propertyName : ${propertyName}`);
}
Les décorateurs de propriété en TypeScript peuvent être utilisés pour ajouter des fonctionnalités supplémentaires aux propriétés d'une classe. Dans l'exemple ci-dessus, nous avons créé un décorateur de propriété appelé "propertyDec" et nous l'avons appliqué à la propriété "nameProperty" de la classe "ClassWithPropertyDec":
class ClassWithPropertyDec {
@propertyDec
nameProperty: string | undefined;
}
Lorsque nous appliquons un décorateur de propriété à une propriété de classe, la fonction du décorateur est appelée avec deux paramètres. Le premier paramètre est l'objet de la définition de classe elle-même, et le second paramètre est le nom de la propriété décorée.
Dans notre exemple, la fonction du décorateur "propertyDec" a été appelée avec l'objet de la définition de classe de "ClassWithPropertyDec" et le nom de la propriété décorée "nameProperty". Dans la fonction du décorateur, nous avons imprimé sur la console la valeur de l'objet "target", qui est l'objet de la définition de classe elle-même, la valeur de la propriété "constructor" de l'objet "target", et le nom de la propriété "propertyName".
Lorsque nous avons exécuté notre code avec le décorateur appliqué à la propriété "nameProperty", nous avons obtenu la sortie suivante dans la console :
target : [object Object]
target.constructor : function ClassWithPropertyDec() {}
propertyName : nameProperty
Nous pouvons voir que le premier paramètre "target" est un objet qui représente la définition de classe elle-même. La deuxième ligne de la sortie de la console montre que cet objet a une propriété "constructor", qui est la fonction constructeur de la classe "ClassWithPropertyDec". Enfin, la dernière ligne de la sortie de la console montre le nom de la propriété que nous avons décorée.
Les décorateurs de propriété peuvent être utilisés pour ajouter des fonctionnalités supplémentaires aux propriétés d'une classe, mais il est important de comprendre leur impact sur le code avant de les utiliser.
Décorateurs de propriétés statiques
Les décorateurs de propriétés peuvent également être appliqués aux propriétés de classe statiques de la même manière que pour les propriétés normales. Cependant, les arguments passés à la fonction du décorateur seront légèrement différents.
class StaticClassWithPropertyDec {
@propertyDec
static staticProperty: string;
}
résultat
target : function StaticClassWithPropertyDec() {}
target.constructor : function Function() { [native code] }
propertyName : staticProperty
Lorsque nous appliquons un décorateur de propriété à une propriété de classe statique, le premier argument passé à la fonction du décorateur sera une fonction représentant la définition de la classe elle-même. La propriété "constructor" de cette fonction sera la fonction constructeur de la classe. Le second argument sera le nom de la propriété décorée.
function propertyDec(target: any, propertyName: string) {
if (typeof (target) === 'function') {
console.log(`class name : ${target.name}`);
} else {
console.log(`class name : `
+ `${target.constructor.name}`);
}
console.log(`propertyName : ${propertyName}`);
}
Dans l'exemple ci-dessus, nous avons appliqué le même décorateur "propertyDec" à une propriété normale et à une propriété statique de classe. Dans la fonction du décorateur, nous avons ajouté une vérification pour savoir si le premier argument est une fonction, et dans ce cas, nous avons affiché le nom de la classe en utilisant la propriété "name" de la fonction. Dans le cas contraire, nous avons affiché le nom de la classe en utilisant la propriété "name" de la propriété "constructor" de l'objet passé en premier argument.
Lorsque nous avons exécuté notre code avec le décorateur appliqué à une propriété normale et à une propriété statique, nous avons obtenu la sortie suivante dans la console :
class name : ClassWithPropertyDec
propertyName : nameProperty
class name : StaticClassWithPropertyDec
propertyName : staticProperty
Nous pouvons voir que nous sommes en mesure de déterminer le nom de la classe et le nom de la propriété décorée, que la propriété ait été marquée statique ou non. Il est important de comprendre que le décorateur de propriété peut avoir un impact sur le comportement de la classe ou de l'application, il est donc important de les utiliser avec prudence.
Décorateurs de méthode
Les décorateurs de méthode sont des fonctions qui peuvent être utilisées pour ajouter du comportement supplémentaire à une méthode de classe en JavaScript ou TypeScript. Ils ont trois paramètres : l'objet cible, le nom de la méthode et un objet descriptor optionnel qui décrit la méthode. Les décorateurs de méthode peuvent être utilisés pour ajouter de la journalisation, de la validation, de la gestion des erreurs et d'autres fonctionnalités à une méthode.
Voici un exemple de décorateur de méthode qui enregistre des messages dans la console chaque fois qu'une méthode est appelée :
function auditLogDec(target: any, methodName: string, descriptor?: PropertyDescriptor) {
let originalFunction = target[methodName];
let auditFunction = function (this: any) {
console.log(`auditLogDec: Overriding ${methodName} called`);
for (let i = 0; i < arguments.length; i++) {
console.log(`auditLogDec: arg ${i} = ${arguments[i]}`);
}
originalFunction.apply(this, arguments);
}
target[methodName] = auditFunction;
return target;
}
class MyClass {
@auditLogDec
myMethod(arg1: string, arg2: number) {
console.log(`myMethod: ${arg1}, ${arg2}`);
}
}
let obj = new MyClass();
obj.myMethod("test", 123);
Dans cet exemple, le décorateur auditLogDec est appliqué à la méthode myMethod de la classe MyClass. Lorsque myMethod est appelée, le décorateur enregistre des messages dans la console avant et après l'appel de la méthode d'origine. Cela permet de suivre le flux d'exécution de la méthode et de déboguer plus facilement les erreurs éventuelles.
En résumé, les décorateurs de méthode sont un outil puissant pour ajouter des fonctionnalités supplémentaires à une méthode de classe en JavaScript ou TypeScript. Ils peuvent être utilisés pour ajouter de la journalisation, de la validation, de la gestion des erreurs et d'autres fonctionnalités à une méthode.
Décorateurs de paramètres
Les décorateurs de paramètres peuvent être utiles pour ajouter des fonctionnalités supplémentaires à une méthode en fonction de la valeur du paramètre.
Lorsqu'un décorateur de paramètres est utilisé, le runtime JavaScript fournit au décorateur trois arguments :
- L'objet cible sur lequel la méthode est définie.
- Le nom de la méthode qui contient le paramètre décoré.
- L'index du paramètre dans la liste des paramètres de la méthode.
Par exemple, voici un exemple de décorateur de paramètres :
function parameterDec(target: any, methodName: string, parameterIndex: number) {
console.log(`target: ${target}`);
console.log(`methodName : ${methodName}`);
console.log(`parameterIndex : ${parameterIndex}`);
}
class ClassWithParamDec {
print(@parameterDec value: string) {
}
}
Dans cet exemple, nous avons défini un décorateur de paramètres nommé parameterDec qui affiche les informations fournies par le runtime JavaScript à propos du paramètre décoré. Nous avons ensuite appliqué ce décorateur à un paramètre de la méthode print de la classe ClassWithParamDec.
Lorsque nous exécutons ce code, le runtime JavaScript affiche les informations suivantes dans la console :
target: [object Object]
methodName : print
parameterIndex : 0
Nous pouvons voir que le runtime JavaScript a fourni au décorateur les informations sur la méthode et le paramètre décoré.
Les décorateurs de paramètres peuvent être utiles pour ajouter des fonctionnalités supplémentaires à une méthode en fonction de la valeur du paramètre. Par exemple, nous pourrions utiliser un décorateur de paramètres pour valider les entrées de la méthode ou pour ajouter des annotations à la documentation de la méthode en fonction des paramètres.
Métadonnées du décorateur
Les décorateurs de paramètres sont une fonctionnalité utile en TypeScript pour ajouter des fonctionnalités à une méthode ou une classe en définissant des décorateurs spécifiques. Cependant, lorsque nous utilisons des décorateurs, nous ne disposons pas de toutes les informations sur les paramètres que nous décorons.
C'est là que les métadonnées de décorateur entrent en jeu. Les métadonnées de décorateur sont des informations supplémentaires qui peuvent être transportées lors de l'utilisation d'un décorateur. Elles nous fournissent plus d'informations sur la méthode ou la classe que nous décorons.
Pour activer cette fonctionnalité, nous devons définir l'indicateur "emitDecoratorMetadata" dans notre fichier "tsconfig.json" sur "true". Voici un exemple de configuration de fichier "tsconfig.json" pour activer les métadonnées de décorateur :
{
"compilerOptions": {
// other compiler options
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Un exemple d'utilisation de métadonnées de décorateurs dans TypeScript est l'ajout de fonctionnalités supplémentaires à une classe ou à une méthode existante. Dans l'exemple suivant, nous définissons un décorateur de méthode appelé "log" qui enregistre des informations sur l'appel de la méthode et le résultat renvoyé:
import "reflect-metadata";
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with arguments: ${args.join(", ")}`);
const result = originalMethod.apply(this, args);
console.log(`Result: ${result}`);
return result;
};
}
class Calculator {
@log
public add(x: number, y: number): number {
return x + y;
}
}
const calculator = new Calculator();
const result = calculator.add(1, 2);
console.log(`Final result: ${result}`);
Dans cet exemple, nous appliquons le décorateur "log" à la méthode "add" de la classe "Calculator". Lorsque la méthode "add" est appelée, le décorateur "log" est exécuté et les informations enregistrées sont affichées dans la console.
Les métadonnées de décorateurs peuvent également être utilisées pour écrire des frameworks pour l'injection de dépendances ou pour générer des outils d'analyse de code. Cependant, il est important de noter que les informations de type utilisées par notre code et le compilateur TypeScript sont compilées dans le JavaScript résultant. Par conséquent, l'utilisation de métadonnées de décorateurs doit être utilisée avec précaution pour éviter de ralentir les performances de l'application.
Conclusion
Nous avons exploré l'utilisation des décorateurs dans TypeScript et comment ils peuvent ajouter des fonctionnalités supplémentaires aux classes, propriétés, méthodes et paramètres de classe. Chaque décorateur a son propre ensemble de paramètres requis et de types de paramètres, en fonction de l'endroit où il doit être utilisé. Les décorateurs sont un outil puissant pour ajouter des fonctionnalités supplémentaires à notre code TypeScript, et les métadonnées de décorateurs fournissent des informations supplémentaires sur nos classes lors de l'exécution. Cependant, il est important de les utiliser avec précaution pour éviter de ralentir les performances de l'application.





