Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Morokhovskyi Roman
Morokhovskyi Roman

Posted on • Edited on

     

Guide to JavaScript Decorators (last proposal)

JavaScript Decorators is a powerful feature that finally reached stage 3 and is already supported by Babel.
Here you can read the official proposalhttps://github.com/tc39/proposal-decorators.

I want to guide you on seamlessly activating the latest decorator feature support for your application. I made a straightforward plan to dive into the decorator feature.

Predefine environment

In order to try the latest feature of decorators, you can create.babelrc file

{"presets":[["@babel/env",{"targets":{"node":"current"}}]],"plugins":[["@babel/plugin-proposal-class-static-block"],["@babel/plugin-proposal-decorators",{"version":"2022-03"}],["@babel/plugin-proposal-class-properties",{"loose":false}]]}
Enter fullscreen modeExit fullscreen mode

And here is the package.json, what you can take as an example:

{"scripts":{"build":"babel index.js -d dist","start":"npm run build && node dist/index.js"},"devDependencies":{"@babel/cli":"^7.19.3","@babel/core":"^7.20.5","@babel/plugin-proposal-decorators":"^7.20.5","@babel/preset-env":"^7.20.2","@babel/preset-stage-3":"^7.8.3"}}
Enter fullscreen modeExit fullscreen mode

Introduction

Probably you are already familiar with Typescript decorators, and you may see it somewhere, it looks like "@myDecorator". Declared decorators start from "@" and can be applied for classes, methods, accessors, and properties.
Here are some examples you may see before:

functionmyDecorator(value:string){// this is the decorator factory, it sets up// the returned decorator functionreturnfunction(target){// this is the decorator// do something with 'target'returnfunction(...args){returntarget.apply(this,args)}};}classUser{@myDecorator('value')anyMethod(){}}
Enter fullscreen modeExit fullscreen mode

Javascript supports natively decorators, which haven’t been part of standard ECMAScript yet but are experimental features. Experimental means it may be changed in future releases. That is what happened. Actually, the latest proposal of the decorators introduced a new syntax and more accurate and purposeful implementation.

Decorators in Typescript

Javascript has been introducing decorators since 2018. And TypeScript support decorators as well, with "experimentalDecorators" enabled option

{"compilerOptions":{..."experimentalDecorators":true}}
Enter fullscreen modeExit fullscreen mode

Notice
TypeScript doesn’t support the last proposal of decorators
https://github.com/microsoft/TypeScript/pull/50820

Nevertheless Typescript provides enough powerful and extended functionality around decorators:

  • Decorator Composition (we can wrap few times an underlying function)
classExampleClass{@first()@second()method(){}}
Enter fullscreen modeExit fullscreen mode

It is equal to:

first(second(method()))// or the same asconstenhancedMethod=first(method());constenhancedTwiceMethod=second(enhancedMethod())
Enter fullscreen modeExit fullscreen mode
  • Parameter Decorators (Unlike JS, TS decorators support decorating params)
classUser{getRole(@injectRBACService){returnRBACService.getRole(this.id)}}
Enter fullscreen modeExit fullscreen mode

The feature is included in the plans forTypescript version 5.0.

How do decorators work?

The decorator basically high order functions. It's a kind of wrapper around another function and enhances its functionality without modifying the underlying function.
Here example of the legacy version:

functionlog(target,name,descriptor){constoriginal=descriptor.value;if(typeoforiginal==='function'){descriptor.value=function(...args){console.log(`Arguments:${args}`);try{constresult=original.apply(this,args);console.log(`Result:${result}`);returnresult;}catch(e){console.log(`Error:${e}`);throwe;}}}returndescriptor;}
Enter fullscreen modeExit fullscreen mode

And the new implementation (that became stage-3 of the proposal):

functionlog(target,{kind,name}){if(kind==='method'){returnfunction(...args){console.log(`Arguments:${args}`);try{constresult=target.apply(this,args);console.log(`Result:${result}`);returnresult;}catch(e){console.log(`Error:${e}`);throwe;}}}}classUser{@loggetName(firstName,lastName){return`${firstName}${lastName}`}}
Enter fullscreen modeExit fullscreen mode

As you can see there are new options, we don't use descriptors anymore to change an object, but we got closer tometaprogramming approach. I really like how Axel Rauschmayer describes what metaprogramming is:

  • We don’t write code that processes user data (programming).
  • We write code that processes code that processes user data (metaprogramming).

Let's take a look closer at the new signature of decorators, here is the new type well described in TS (but hasn’t merged to master yet), we will use it just as an example

typeDecorator=(value:DecoratedValue,context:{kind:string;name:string|symbol;addInitializer(initializer:()=>void):void;// Don’t always exist:static:boolean;private:boolean;access:{get:()=>unknown,set:(value:unknown)=>void};})=>void|ReplacementValue;
Enter fullscreen modeExit fullscreen mode
  • Kind parameter can be:
    'class'
    'method'
    'getter'
    'setter'
    'accessor'
    'field'
    Kind property tells the decorator which kind of JavaScript construct it is applied to.

  • Name is the name of a method or field in a class.

  • addInitializer allows you to execute code after the class itself or a class element is fully defined.

Auto-accessors

The decorators' proposal introduces a new language feature: auto-accessors.
To understand what the auto-accessors feature is, let's take a look at the below example:

// New "accessor" keywordclassUser{accessorname='user';constructor(name){this.name=name}}// it's the same asclassUser{#name='user';constructor(name){this.name=name}getname(){returnthis.#name;}setname(value){this.#name=value;}}
Enter fullscreen modeExit fullscreen mode

Auto-accessor is a shorthand syntax for standard getters and setters that we get used to implementing in classes.
We can apply decorators for accessors easily with the new proposal:

functionaccessorDecorator({get,set},{name,kind}){if(kind==='accessor'){return{init(){return'initial value';},get(){constvalue=get.call(this);...returnvalue;},set(newValue){constoldValue=get.call(this);...set.call(this,newValue);},};}}classUser{@accessorDecoratoraccessorname='user';constructor(name){this.name=name}}
Enter fullscreen modeExit fullscreen mode

Fields Decorator

How can we decorate fields by legacy approach?
Here code example:

functionfieldDecorator(target,name,descriptor){return{...descriptor,writable:falseinitializer:()=>'EU'}}classCustomer{@fieldDecoratorcountry='USA';getCountry(){returnthis.country}}constcustomer=newCustomer('john')customer.getCountry();// 'EU' instead of USA, because of initializercustomer.country='DE'// TypeError: Cannot assign to read only property 'country' of object '#<Customer>'
Enter fullscreen modeExit fullscreen mode

Limitation legacy decorators:

  • We can't decorate private fields
  • it’s always hacky to decorate efficiently on accessors of fields

The new proposal is more flexible:

functionreadOnly(value,{kind,name}){if(kind==='field'){returnfunction(){if(!this.readOnlyFields){this.readOnlyFields=[]}this.readOnlyFields.push(name)}}if(kind==='class'){returnfunction(...args){constobject=newvalue(...args);for(constreadOnlyKeyofobject.readOnlyFields){Object.defineProperty(object,readOnlyKey,{writable:false});}returnobject;}}}@readOnlyclassCustomer{@readOnlycountry;constructor(country){this.country=country}getCountry(){returnthis.country}}constcustomer=newCustomer();customer.getCountry();// 'USA'customer.country='EU'// // TypeError: Cannot assign to read only property 'country' of object '#<Customer>'
Enter fullscreen modeExit fullscreen mode

As you can see, we don't have access to the property descriptor. Still, we can implement it differently, collect all the not writable fields, and set "writable: false" through the class decorator.

Conclusion

I think this is a whole new level, as developers can dive even more into the world of metaprogramming and look forward to the release of Typescript 5.0 and when the new proposal becomes part of the EcmaScript standard.

Follow me on 🐦Twitter if you want to see more content.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software Developer
  • Location
    Ukraine
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp