This article is the second part of the series:
In the previous article, we found out what types of decorators we can use in TypeScript.
We also learned how to implement a method decorator and answered basic questions about how decorators work in TypeScript:
__decorate
function __decorate
?In this article, we will introduce two new types of decorators: the property decorator ( PropertyDecorator
) and the class decorator ( ClassDecorator
).
We already know that the property decorator's signature looks like this:
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
We can use the logProperty
property logProperty
as follows:
class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } }
If you compile this code in JavaScript, we find that the __decorate
function (which we dealt with in the first part ) is called in it, but this time it lacks the last parameter (the property descriptor obtained through Object.getOwnPropertyDescriptor
)
var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } __decorate([ logProperty ], Person.prototype, "name"); return Person; })();
The decorator receives 2 arguments (prototype and key), rather than 3 (prototype, key and property descriptor), as is the case with the method decorator.
Another important point: this time the TypeScript compiler does not use the value returned by the __decorate
function in order to redefine the original property, as it did with the method decorator:
Object.defineProperty(C.prototype, "foo", __decorate([ log ], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo") ) );
Now that we know that the property decorator takes the prototype of the class being decorated and the name of the field being decorated as arguments and returns nothing, let's implement the logProperty
:
function logProperty(target: any, key: string) { // var _val = this[key]; // var getter = function () { console.log(`Get: ${key} => ${_val}`); return _val; }; // var setter = function (newVal) { console.log(`Set: ${key} => ${newVal}`); _val = newVal; }; // , if (delete this[key]) { // Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); } }
The decorator above declares a variable named _val
and stores in it the value of the property being decorated (since this
in this context indicates the class prototype, and key
indicates the name of the property).
Next, the getter
functions (used to get the value of a property) and setter
(used to set the value of a property) are declared. Both functions have access to _val
thanks to the closures created when they were declared. This is where we add additional behavior to the property , in
In this case, outputting a line to the log when the property value changes.
Then, the operator delete
used to remove the original property from the prototype class.
Note that the delete
throws an exception in "strict mode" if the field to be deleted is its own unconfigurable property ( false
returns false
in normal mode).
If the removal was successful, the Object.defineProperty()
method is used to create a new property with the original name, but this time it uses the previously declared getter
and setter
functions.
Now the decorator will display in the console changes to the properties each time we receive or set the value in it.
var me = new Person("Remo", "Jansen"); // Set: name => Remo me.name = "Remo H."; // Set: name => Remo H. me.name; // Get: name Remo H.
As we already know, the class decorator signature looks like this:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
We can use a decorator named logClass
like this:
@logClass class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } }
After compilation in JavaScript, the __decorate
function is __decorate
, and this time it does not already have the last two arguments:
var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } Person = __decorate([ logClass ], Person); return Person; })();
Note that the compiler passes to __decorate
Person
, not Person.prototype
.
Also note that the compiler uses the return value in order to override the original constructor.
Person = __decorate(/* ... */);
Remember that a class decorator must return a constructor function .
Now we can implement logClass
:
function logClass(target: any) { // var original = target; // function construct(constructor, args) { var c : any = function () { return constructor.apply(this, args); } c.prototype = constructor.prototype; return new c(); } // var f : any = function (...args) { console.log("New: " + original.name); return construct(original, args); } // , instanceof f.prototype = original.prototype; // ( ) return f; }
The decorator above creates the original
variable and saves the constructor of the class being decorated to it.
Next, the auxiliary function construct
declared, which allows us to create instances of the class.
Then we create the variable f
, which will be used as a new constructor. It calls the original constructor, and also logs the name of the class to be instantiated to the console. This is where we add new behavior to the original class.
The prototype of the original constructor is copied to prototype f
, so the instanceof
operator works with Person
objects.
It remains to simply return the new constructor, and our implementation is ready.
Now the decorator will display the class name in the console each time it is instantiated:
var me = new Person("Remo", "Jansen"); // New: Person me instanceof Person; // true
Now we have a deep understanding of how 3 of 4 types of decorators work in TypeScript.
In the next article, we will examine the remaining type (parameter decorator), as well as learn how to create universal decorators that can be applied to classes, properties, methods, and parameters.
Source: https://habr.com/ru/post/277321/
All Articles