Décorateurs ES7

ES6 n'est pas encore là qu'on aborde déjà des fonctionnalités futures d'ES7. Je vais parler ici des décorateurs. Ce sont des annotations qui permettent de modifier une classe et ses propriétés au moment de son implémentation.

Lors de la transformation d'une classe ES6 en ES5, le transcluder va faire appel à Object.defineProperty pour générer une propriété. Ainsi, si nous avons la classe suivante :

// test.js
export class Test {  
  pwet () { return 'lol'; }
}

Sera alors généré en ES5 :

// test.js
function Test () {}  
Object.defineProperty(Test.prototype, 'pwet', {  
  value: function () { return 'lol'; },
  enumerable: false,
  configurable: true,
  writable: true
});

Le décorateur va nous permettre de nous placer juste avant la génération de cette propriété et d'altérer sa configuration. Il se représente sous la forme d'une annotation préfixée de "@" et pointe vers une fonction du même nom qui prendra en paramètres la cible (la classe), la clé (le nom de la propriété) et la description de la propriété. On pourra alors modifier cet objet descriptor afin de changer la définition de la propriété.

Prenons comme exemple un decorator readonly qui nous permettra de définir une propriété comme étant en lecture seule. Cela se concrétise en ES6 par l'ajout de l'attribut writable à false. On va donc écrire une fonction readonly qui ajoute cet attribut au descripteur :

// decorators.js
export function readonly(target, key, descriptor) {  
  descriptor.writable = false;
  return descriptor;
}

On va ensuite décorer notre méthode pwet:

// test.js
import {readonly} from '../decorators';

export class Test {  
  @readonly
  pwet () {
    return 'lol';
  }
}

Ainsi, si je tente de redéfinir ma propriété pwet de la sorte :

// index.js
import {Test} from '../test.js';

let test = new Test();  
test.pwet = 'lol';  

Je me prends une erreur : "TypeError: Cannot assign to read only property 'pwet' of #".

On peut créer des décorateurs avancés prenant en compte des attributs. On va regarder le décorateur inject qui a été écrit pour AngularJS afin d'injecter facilement des dépendances. L'idée est donc d'annoter la classe en lui indiquant la liste des modules à injecter dans le service :

import {inject, register} from '../system/decorators';

@register({
  type:'controller'
})
@inject('$scope', 'MyCustomService')
export class MainCtrl {  
  constructor($scope, MyCustomService) {
    this.$scope = $scope;
    this.MyCustomService = MyCustomService;
  }
}

Le but étant au final d'ajouter l'attribut $inject au constructeur de la classe. Notre décorateur sera alors une fonction qui retourne une fonction générée à partir des attributs envoyés :

export function inject (...components) {  
  return function decorate (target, key, descriptor) {
    if (descriptor) {
      target = descriptor.value;
    }

    target.$inject = components;

    return descriptor;
  };
}

Ces decorateurs sont déjà utilisés de base dans AngularJS 2.0, on peut aussi les utiliser de façon optionnelle dans Aurelia.io et il est possible aussi de les utiliser dans n'importe quel projet grâce à Babel, comme le propose par exemple ce projet Angular/ES6. Si vous voulez en savoir plus à propos des décorateurs…

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu