On Github nmackey / presentation-decorators
By: Nicholas Mackey
Who is already using decorators? How many people have written their own?@someDecorator
@autobind
@NgModule()
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