By: Nicholas Mackey
@someDecorator
@autobind
@NgModule()
Decorators offer a convenient declarative syntax to modify the shape of class declarations.From the decorators draft spec (Yehuda Katz, Brian Terlson)
function someDecorator() {
returns function(target, name, descriptor){};
}
There isn't anything you can do with decorators that you can't already do.
But they make your life better :)
in flux...
Babel 5 (decorators in)
Babel 6 (decorators out)
But almost back?
You can use the 'legacy' plugin to get support for the original spec now
This is where we will focus.
npm install --save-dev babel-plugin-transform-decorators-legacy
.babelrc
{
"presets": [ "es2015" ],
"plugins": [
"transform-decorators-legacy", // before class-properties
"transform-class-properties" // optional
]
}
import { readonly } from 'decorators';
class Test {
@readonly
grade = 'B';
}
const mathTest = new Test();
mathTest.grade = A;
// Uncaught TypeError: Cannot assign to read only property 'grade'...
export function readonly(target, name, descriptor){
descriptor.writable = false;
return descriptor;
}
function someDecorator() {
returns function(target, name, descriptor){};
}
Same descriptors that are used for Object.defineProperty()
ES5 feature, part of Object.defineProperty()
2 types - data & accessor
{
value: 'test', // could be number, object, function
configurable: true, // can be deleted if true, defaults to false
enumerable: true, // show in for in if true, defaults to false
writable: true // can be updated, defaults to false
}
{
get: function() {
return 'test';
},
set: function(value) {
this.value = value;
},
configurable: true, // can be deleted if true, defaults to false
enumerable: true // show in for in if true, defaults to false
}
import { decorator } from 'decorators';
class foo {
@decorator
bar() {}
}
// target: class prototype
// name: 'bar'
// descriptor: {
// value: bar,
// writable: true,
// enumerable: false,
// configurable: true
// }
export function decorator(target, name, descriptor) {
// do stuff
};
ES5
var View = Backbone.View.extend({
tagName: 'li',
className: 'stuff',
events: {
'click .btn': 'handleClick'
},
initialize: function() {},
render: function() {},
handleClick: function() {}
});
ES6/2015
class View extends Backbone.View {
get tagName() {
return 'li';
}
get className() {
return 'stuff';
}
get events() {
return {
'click .btn': 'handleClick'
};
}
initialize() {}
render() {}
handleClick() {}
}
Decided to look into two things
go from this
class View extends Backbone.View {
get tagName() {
return 'li';
}
get className() {
return 'stuff';
}
}
to this
import { tagName, className } from 'decorators';
@tagName('li')
@className('stuff')
class View extends Backbone.View {}
(more on multiple decorators in a second)
go from this
class View extends Backbone.View {
get events() {
return {
'click .btn': 'handleClick',
'keypress input': 'handleKeyPress'
};
}
handleClick {}
handleKeypress {}
}
to this
import { on } from 'decorators';
class View extends Backbone.View {
@on('click .btn')
handleClick {}
@on('click input')
handleKeypress {}
}
Order matters
evaluated top to bottom, executed bottom to top
class Bar {
@F
@G
foo() {}
}
F(G(foo()))
Sets the classname on the prototype
import { className } from 'decorators';
@className('stuff')
class View extends Backbone.View {}
export function className(value) {
return function(target) {
target.prototype.className = value;
};
}
Sets an event trigger on the method
import { on } from 'decorators';
class View extends Backbone.View {
@on('click .btn')
handleClick {}
}
export function on(eventName) {
return function(target, name) {
if (!target.events) {
target.events = {};
}
target.events[eventName] = name;
};
}
great decorator library meant to be used with the babel-legacy plugin
Binds the class to 'this' for the method or class
import { autobind } from 'core-decorators';
class test {
@autobind
handleEvent() {
// 'this' is the class
}
}
Creates a new debounced method that will wait the given time in ms before executing again
import { debounce } from 'core-decorators';
class test {
content = '';
@debounce(500)
updateContent(content) {
this.content = content;
}
}
Calls console.warn() with a deprecation message.
import { deprecate } from 'core-decorators';
class test {
@deprecate('stop using this')
oldMethod() {
// do stuff
}
}
Applies decorators without transpiling
import { applyDecorators } from 'core-decorators';
class Foo {
getFoo() {
return this;
}
}
applyDecorators(Foo, {
getFoo: [autobind]
});
export function decorator(target, name, descriptor) {
// modify the existing descriptor
}
export function maintainState(target) {
Object.assign(target.prototype, {
save,
saveState,
restoreState,
hasStateChanged
});
}
export function decorator() {
return function(target, name, descriptor) {
// you want the context of this function to
// be the same as the method you are decorating
};
}
Presentation online at nmackey.com/presentation-decorators/ Slides available at github.com/nmackey/presentation-decorators Example code available at github.com/nmackey/presentation-decorators-examples
Web: nmackey.com / Twitter: @nicholas_mackey / Github: nmackey