JavaScript Decorators : Ce qu’ils sont et quand les utiliser
On janvier 26, 2022 by adminAvec l’introduction de ES2015+, et alors que la transpilation est devenue courante, beaucoup d’entre vous auront rencontré de nouvelles fonctionnalités du langage, soit dans du code réel, soit dans des tutoriels. L’une de ces fonctionnalités qui fait souvent gratter les gens lorsqu’ils les rencontrent pour la première fois sont les décorateurs JavaScript.
Les décorateurs sont devenus populaires grâce à leur utilisation dans Angular 2+. Dans Angular, les décorateurs sont disponibles grâce à TypeScript, mais en JavaScript, ils sont actuellement une proposition de stade 2, ce qui signifie qu’ils devraient faire partie d’une future mise à jour du langage. Jetons un coup d’œil à ce que sont les décorateurs, et comment ils peuvent être utilisés pour rendre votre code plus propre et plus facilement compréhensible.
Qu’est-ce qu’un décorateur?
Dans sa forme la plus simple, un décorateur est simplement une façon d’envelopper un morceau de code avec un autre – littéralement le « décorer ». C’est un concept dont vous pourriez bien avoir entendu parler précédemment comme la composition fonctionnelle, ou les fonctions d’ordre supérieur.
Cela est déjà possible en JavaScript standard pour de nombreux cas d’utilisation, simplement en faisant appel à une fonction pour en envelopper une autre :
function doSomething(name) { console.log('Hello, ' + name);}function loggingDecorator(wrapped) { return function() { console.log('Starting'); const result = wrapped.apply(this, arguments); console.log('Finished'); return result; }}const wrapped = loggingDecorator(doSomething);
Cet exemple produit une nouvelle fonction – dans la variable wrapped
– qui peut être appelée exactement de la même manière que la fonction doSomething
, et fera exactement la même chose. La différence est qu’elle effectuera une certaine journalisation avant et après l’appel de la fonction enveloppée :
doSomething('Graham');// Hello, Grahamwrapped('Graham');// Starting// Hello, Graham// Finished
Comment utiliser les décorateurs JavaScript
Les décorateurs utilisent une syntaxe spéciale en JavaScript, par laquelle ils sont préfixés par un symbole @
et placés immédiatement avant le code à décorer.
Note : au moment de la rédaction, les décorateurs sont actuellement sous forme de « Stage 2 Draft », ce qui signifie qu’ils sont en grande partie terminés mais encore sujets à des modifications.
Il est possible d’utiliser autant de décorateurs sur le même morceau de code que vous le désirez, et ils seront appliqués dans l’ordre où vous les déclarez.
Par exemple :
@log()@immutable()class Example { @time('demo') doSomething() { // }}
Cela définit une classe et applique trois décorateurs – deux à la classe elle-même, et un à une propriété de la classe :
-
@log
pourrait enregistrer tous les accès à la classe -
@immutable
pourrait rendre la classe immuable – peut-être qu’elle appelleObject.freeze
sur les nouvelles instances -
@time
enregistrera le temps qu’une méthode prend pour s’exécuter et enregistrera ceci avec une balise unique.
À l’heure actuelle, l’utilisation des décorateurs nécessite le support du transpilateur, car aucun navigateur actuel ou aucune version de Node ne les prend encore en charge. Si vous utilisez Babel, cela est activé simplement en utilisant le plugin transform-decorators-legacy.
Note : l’utilisation du mot « legacy » dans ce plugin est parce qu’il supporte la façon dont Babel 5 gère les décorateurs, qui pourrait bien être différente de la forme finale quand ils seront standardisés.
Pourquoi utiliser les décorateurs ?
Alors que la composition fonctionnelle est déjà possible en JavaScript, il est nettement plus difficile – voire impossible – d’appliquer les mêmes techniques à d’autres morceaux de code (par exemple, les classes et les propriétés de classe).
La proposition de décorateur ajoute le support des décorateurs de classes et de propriétés qui peuvent être utilisés pour résoudre ces problèmes, et les futures versions de JavaScript ajouteront probablement le support des décorateurs pour d’autres zones de code gênantes.
Les décorateurs permettent également une syntaxe plus propre pour appliquer ces enveloppes autour de votre code, résultant en quelque chose qui nuit moins à l’intention réelle de ce que vous écrivez.
Différents types de décorateur
À l’heure actuelle, les seuls types de décorateur qui sont supportés sont sur les classes et les membres des classes. Cela inclut les propriétés, les méthodes, les getters et les setters.
Les décorateurs ne sont en fait rien de plus que des fonctions qui renvoient une autre fonction, et qui sont appelées avec les détails appropriés de l’élément décoré. Ces fonctions décoratrices sont évaluées une fois lors de la première exécution du programme, et le code décoré est remplacé par la valeur de retour.
Décorateurs de membres de classe
Les décorateurs de propriétés sont appliqués à un seul membre d’une classe – qu’il s’agisse de propriétés, de méthodes, de getters ou de setters. Cette fonction de décorateur est appelée avec trois paramètres :
-
target
: la classe sur laquelle se trouve le membre. -
name
: le nom du membre dans la classe. -
descriptor
: le descripteur du membre. C’est essentiellement l’objet qui aurait été passé à Object.defineProperty.
L’exemple classique utilisé ici est @readonly
. Ceci est mis en œuvre aussi simplement que:
function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}
Littéralement, la mise à jour du descripteur de propriété pour mettre le drapeau « writable » à false.
Cela est ensuite utilisé sur une propriété de classe comme suit:
class Example { a() {} @readonly b() {}}const e = new Example();e.a = 1;e.b = 2;// TypeError: Cannot assign to read only property 'b' of object '#<Example>'
Mais nous pouvons faire mieux que cela. Nous pouvons en fait remplacer la fonction décorée par un comportement différent. Par exemple, enregistrons toutes les entrées et sorties:
function log(target, name, descriptor) { const original = descriptor.value; if (typeof original === 'function') { descriptor.value = function(...args) { console.log(`Arguments: ${args}`); try { const result = original.apply(this, args); console.log(`Result: ${result}`); return result; } catch (e) { console.log(`Error: ${e}`); throw e; } } } return descriptor;}
Cela remplace la méthode entière par une nouvelle qui enregistre les arguments, appelle la méthode originale et enregistre ensuite la sortie.
Notez que nous avons utilisé l’opérateur spread ici pour construire automatiquement un tableau à partir de tous les arguments fournis, ce qui est l’alternative plus moderne à l’ancienne valeur arguments
.
Nous pouvons voir cela en utilisation comme suit:
class Example { @log sum(a, b) { return a + b; }}const e = new Example();e.sum(1, 2);// Arguments: 1,2// Result: 3
Vous remarquerez que nous avons dû utiliser une syntaxe légèrement drôle pour exécuter la méthode décorée. Cela pourrait couvrir un article entier en soi, mais en bref, la fonction apply
vous permet d’appeler la fonction, en spécifiant la valeur this
et les arguments pour l’appeler avec.
En montant d’un cran, nous pouvons faire en sorte que notre décorateur prenne quelques arguments. Par exemple, réécrivons notre décorateur log
comme suit:
function log(name) { return function decorator(t, n, descriptor) { const original = descriptor.value; if (typeof original === 'function') { descriptor.value = function(...args) { console.log(`Arguments for ${name}: ${args}`); try { const result = original.apply(this, args); console.log(`Result from ${name}: ${result}`); return result; } catch (e) { console.log(`Error from ${name}: ${e}`); throw e; } } } return descriptor; };}
Cela devient plus complexe maintenant, mais quand nous le décomposons, nous avons ceci:
- Une fonction,
log
, qui prend un seul paramètre :name
. - Cette fonction renvoie ensuite une fonction qui est elle-même un décorateur.
Ce dernier est identique au décorateur log
précédent, sauf qu’il utilise le paramètre name
de la fonction extérieure.
Ce dernier est ensuite utilisé comme suit:
class Example { @log('some tag') sum(a, b) { return a + b; }}const e = new Example();e.sum(1, 2);// Arguments for some tag: 1,2// Result from some tag: 3
D’emblée, nous pouvons voir que cela nous permet de distinguer les différentes lignes de journal en utilisant une balise que nous avons fournie nous-mêmes.
Cela fonctionne parce que l’appel de fonction log('some tag')
est évalué par le runtime JavaScript directement, puis la réponse de cela est utilisée comme décorateur pour la méthode sum
.
Décorateurs de classe
Les décorateurs de classe sont appliqués à la définition de classe entière en une seule fois. La fonction du décorateur est appelée avec un seul paramètre qui est la fonction du constructeur qui est décorée.
Notez que cela est appliqué à la fonction du constructeur et non à chaque instance de la classe qui est créée. Cela signifie que si vous voulez manipuler les instances, vous devez le faire vous-même en retournant une version enveloppée du constructeur.
En général, ceux-ci sont moins utiles que les décorateurs de membres de classe, car tout ce que vous pouvez faire ici, vous pouvez le faire avec un simple appel de fonction exactement de la même manière. Tout ce que vous faites avec ceux-ci doit finir par retourner une nouvelle fonction constructeur pour remplacer le constructeur de la classe.
Retournons à notre exemple de journalisation, écrivons-en un qui consigne les paramètres du constructeur :
function log(Class) { return (...args) => { console.log(args); return new Class(...args); };}
Ici nous acceptons une classe comme argument, et retournons une nouvelle fonction qui agira comme le constructeur. Celui-ci enregistre simplement les arguments et renvoie une nouvelle instance de la classe construite avec ces arguments.
Par exemple:
@logclass Example { constructor(name, age) { }}const e = new Example('Graham', 34);// console.log(e);// Example {}
Nous pouvons voir que la construction de notre classe Exemple va enregistrer les arguments fournis et que la valeur construite est bien une instance de Example
. Exactement ce que nous voulions.
Passer des paramètres dans les décorateurs de classe fonctionne exactement de la même manière que pour les membres de classe:
function log(name) { return function decorator(Class) { return (...args) => { console.log(`Arguments for ${name}: args`); return new Class(...args); }; }}@log('Demo')class Example { constructor(name, age) {}}const e = new Example('Graham', 34);// Arguments for Demo: argsconsole.log(e);// Example {}
Real World Examples
Core Decorators
Il y a une bibliothèque fantastique appelée Core Decorators qui fournit quelques décorateurs communs très utiles qui sont prêts à être utilisés dès maintenant. Ceux-ci permettent généralement des fonctionnalités communes très utiles (par exemple, le timing des appels de méthode, les avertissements de dépréciation, s’assurer qu’une valeur est en lecture seule) mais en utilisant la syntaxe beaucoup plus propre des décorateurs.
React
La bibliothèque React utilise très bien le concept de composants d’ordre supérieur. Il s’agit simplement de composants React qui sont écrits comme une fonction et qui enveloppent un autre composant.
Achetez notre cours Premium : React The ES6 Way
Ceux-ci sont un candidat idéal pour l’utilisation en tant que décorateur, car il y a très peu de choses que vous devez changer pour le faire. Par exemple, la bibliothèque react-redux a une fonction, connect
, qui est utilisée pour connecter un composant React à un magasin Redux.
En général, cela serait utilisé comme suit:
class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
Pourtant, en raison de la façon dont la syntaxe des décorateurs fonctionne, cela peut être remplacé par le code suivant pour obtenir exactement la même fonctionnalité:
@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {}
MobX
La bibliothèque MobX fait un usage intensif des décorateurs, vous permettant de marquer facilement les champs comme Observable ou Computed, et de marquer les classes comme Observateurs.
Summary
Les décorateurs de membres de classe fournissent un très bon moyen d’envelopper du code à l’intérieur d’une classe de manière très similaire à la façon dont vous pouvez déjà le faire pour les fonctions autonomes. Cela fournit un bon moyen d’écrire du code d’aide simple qui peut être appliqué à beaucoup d’endroits d’une manière très propre et facile à comprendre.
La seule limite à l’utilisation d’une telle facilité est votre imagination!
Laisser un commentaire