No need to wait for the holidays, start Decorating now

Andrico Karoulla
codeburst
Published in
8 min readJun 20, 2018

--

ES2018+, a.k.a ESnext, a.k.a ECMA-262 is giving us devs some shiny new toys.

Every couple of months the ECMAscript committee meets up. It’s usually an opportunity for the big man Brendan Eich to catch up with old pals, and if there’s still time afterwards, discuss the future of JavaScript. You know, that programming language that 12 million developers use professionally, at least according to this website. Well, the committee is meeting in July and there are some big items on the agenda. As well as a series of discussions regarding the next steps for the language, there’s also a proposal to move JavaScript decorators to stage 3.

A proposal that’s reached stage 4 is considered finished. The only thing stopping us from using a stage 4 feature is browser compatibility, which is only a couple days wait, right? A feature that’s reached stage 3 is considered almost finished, in the sense that only limited changes are expected to be made to the semantics and syntax based on user feedback. The APIs in the spec should also be fully described, so reading up on the spec is a great way to keep ahead of the curve. For a feature to even reach this stage requires months of work put in by dedicated champions. When a feature reaches stage 3 it’s likely that Babel and TypeScript will offer the capability to use them in some form inside your own projects. Currently Babel and TypeScript offer their own flavour of decorators, which is something I’ll address towards the end of the article.

Note: I’ll be using the terms method and function interchangeably, but when I refer to a function as a method I’m specifically referring to it as being defined inside of a class. Also, it’s worth knowing a little bit about closures and JavaScript classes, specifically babel’s ES6 class syntax. Understanding these aren’t required for the article, but it’ll help you get the most out of it. Oh, and I’ll also be using the JavaScript Proxy API for one example so you’re gonna have to deal with that.

September

Decorators allow us to concisely extend the behaviour of classes using simple annotations. Decorators are no more than functions, which means that there’s very little we need to learn before we can start using them. So let’s do some role-playing…

You’ve been contracted to create a payment system for an ice-cream parlour (being paid in buckets of Ben and Jerry’s Karamel Sutra). The ice-cream parlour has an outlet in London and another in Manchester with the two stores being identical, aside from the price of ice-cream. Each item in London costs 1.2x more than the store in Manchester, to accommodate for the store’s £12,000 a day rent. We don’t want any of the sneaky staff members editing the price to get cheaper ice-cream, so we need to add a safeguard, which we’ll implement using decorators.

We start by creating our LondonIceCream class which has a single field of iceCreamPrice, which gets initialised using the BASE_ICE_CREAM_PRICE constant. I’ve also gone and used the setPriceAndLock decorator on iceCreamPrice.

Underneath the class is the decorator definition, which you may have noticed is little more than a function. The function receives the target object as a parameter, which is the iceCreamPrice field, and returns the decorated target. We’re altering our field in two ways, in lines 10–13 we’re changing the field’s initializer() function to return the original value multiplied by 1.2, which gets returned in line 16. The initializer() function is what’s called by the field to compute the initial value and in the case without the decorator, it would be 1.99. We’re also making changes to the descriptor object by changing the writable property to false in line 20. People familiar with Object.defineProperty() will have seen this writable field before, by changing it to false we’re preventing the value from being reassigned. It’s important to note that the shape of the data we’re returning is the same as the target object that’s being passed as the argument.

And there we go, our first decorator!

October

Enough with the contrived examples, let’s see why we would use them in our own projects. Here’s a much more practical, real-world example of when and why you’d use decorators.

This example is a great use case for those who prefer to not use arrow functions inside of classes. Check out the Form class above and you can see that we add the bound decorator above each method to prevent us from having to pollute the constructor with some nasty this.method.bind(this) statements, which is precisely what’s happening in the FuglyForm class.

There are some arguments encouraging the use of decorators in this fashion. Functions created via arrow functions can cause discrepancies when dealing with inheritance, and can make mocking more challenging. This is because a class method defined using a function declaration will be defined on the prototype, while one defined using a function expression won’t. This is more clearly and comprehensively expressed on the official TC39 GitHub repo, which can be found here

Regardless on where you stand, we need to give Decorators some credit. They’re succinct, expressive and they double-up as annotation, which self-document your code.

November

We’ve already defined our delicious, creamy ice-cream decorator. So let’s create our @bound decorator, which is much more savoury.

A quick overview and then we’ll break this file down line by line. The decorator is creating a brand new field which uses an initializer function to bind and return the original method’s value. Right at the end, we return both the original method and the bound function field so the original method still exists on the prototype. Here it is explained in more detail…

line 1 — function bound(target) {...} define the function.

line 2 — const { kind, key, descriptor } = target; destructure the important data from the target. We’ve seen the descriptor before, but the key and kind are new. The key is the name of the target as a string. kind details whether it’s a method, field or class.

line 3 — assert(kind == ‘method'); throw an error if the decorator is used on an incorrect target kind.

line 5 — destructure the value property, which contains the method that we decorated, i.e. submit()/change()

line 6 — create a new initializer function

line 7 — return value.bind(this); bind the value (a.k.a. the method that we decorated) to the context

line 10 — let boundTarget = { …descriptor, value: undefined }; create a separate instance of our target, which will be a part of the bound function field mentioned above.

line 12— return the original target

line 13 — return { ...target, extras [{...}]}; return a new field which contains the new initializer and a copy of the method.

Note: For learning more about taxonomy of these target objects, I’d again recommend checking out the official TC39 decorators readme.md.

December

There are a few Dos and Do Not Dos when placing decorators. We can decorate a whole class as well the class`s field and method declarations, but decorators can’t be used to decorate function declarations. You can see these as code below.

Creating a type validator for our class fields is another great use-case. Since JavaScript isn’t a strongly typed language we’d need to make our own type system (or use TypeScript) to prevent any weird type coercion from ruining our perfect code.

The use of the Proxy API may make the example seem a little daunting, but it’s one of the most simple in the article. We’ve created a decorator called stringValidator which is placed on our class fields. It makes sure that as we initialize our variables, we’re doing so only with the correct value type.

Aside from lines 5 to 13, everything else has already been covered in the article. Line 5 creates a new instance of the Proxy object, which acts as an intermediary between the initial call of our class fields and the moment the initial value gets set. Our proxy will only intercept calls to set the value, because we’ve told our proxy to set a trap for it, line 6.

So the proxy lays a trap for the set method. If it gets called, which it does when the value gets initialized, our proxy intercepts the call and runs our own logic we’ve written in lines 7–9, which checks to see if the type is a string. If it is then we return true and everything else runs like normal. If we try initialize with a different time type, our Proxy throws a TypeError which causes our application to shit the bed at runtime. Enjoy your flawless new type system.

Stumbled across Decorator, the band, while researching. They’re pretty kickass. https://www.youtube.com/watch?v=7IWzheHUmoM

January

Decorators are a perfect example of the ‘easy to learn, difficult to master’ adage.

Before I kicked off the research for the article I saw decorators as an alternative to Function Composition, but it took just a little digging to realise that they’re more than that.

  • They’re easy to create and integrate in your code
  • They’re a powerful way to alter class, field or method definitions
  • They can be used for analytics, validation and logging
  • They’re easy to reuse throughout your codebase

If this article has given you a little inspiration then go ahead and use decorators throughout your own projects. You’ll also do well to show them off to your co-workers. There is one minor issue though…

While TypeScript and Babel have their own flavours of Decorators, in babel.7.0.0beta51 only legacy decorators can be used, which are decorators using stage 1 proposal syntax. The stage 2/3 syntax should be added in the near future though, in fact it may even be ready by the time you read this. I know this sounds a little counter intuitive, creating a tutorial for a feature that people can’t use just yet, but it’s for the sake of future-proofing. I specifically chose to use stage 2/3 syntax as it’s described in the official proposal as it’s *likely* to stay the same.

If you still want to use decorators, albeit with legacy syntax, you’ll need to install a few babel dependencies first.

> npm install -D @babel/cli @babel/core @babel/plugin-proposal-decorators @babel/preset-env @babel/plugin-proposal-class-properties

Update your .babelrc file to contain the following:

{
"plugins": [
[
"@babel/plugin-proposal-decorators", { "legacy": true }
],
[
"@babel/plugin-proposal-class-properties", { "loose": true }
],
]
}

Now when babel compiles your code, it’ll look out for the decorator syntax and compile it down to regular ol’ browser friendly javascript.

Thanks for reading!

Decorators are just one of a bunch of great ESnext features that make up my talk Enter ES2018+. If you’re interested in hosting me at a conference, meetup or as a speaking guest for any engagement then DM me on twitter!

I hope this article taught you something new. I post regularly, so if you want to keep up to date with my latest releases then you can follow me. And remember, the longer you hold the clap button, the more claps you can give. 👏👏👏

You an also check out my other articles below:

How to use Apollo’s brand new Query components to manage local state

Add a touch of Suspense to your web app with React.lazy()

No need to wait for the holidays, start Decorating now

Managing local state with Apollo and Higher Order Components

The React Conference drinking game

Develop and Deploy your own React monorepo app in under 2 hours, using Lerna, Travis and Now

✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.

--

--

Hey, I'm @andricokaroulla, Software Developer, and creator of Component Odyssey