Share what you learned from studying Decorators
The concept is introduced
Decorators are one of the most powerful features TypeScript provides, enabling us to extend the functionality of classes and methods in a clean, declarative way. Decorators are currently a phase 2 proposal in JavaScript, but have become popular in the TypeScript ecosystem and are being used by major open source projects such as Angular.
I’m working on a project using Angular8 and have a lot of exposure to Decorators, so I wrote an article about Decorators over the weekend in the hope of helping students who are learning about Decorators. Without further ado, let’s get down to business.
Start using decoratorsDecorators
Json to enable the experimentalDecorators compiler option:
Command-line: TSC –target ES5 –experimentalDecorators
tsconfig.json:
{
"compilerOptions": {
"target": "ES5"."experimentalDecorators": true}}Copy the code
Let’s first clarify two concepts:
- Right now decorators are essentially a function,
@expression
Is actually a grammatical sugar,expression
The evaluation must also be a function, which is called at run time with the embellished declaration information passed in as an argument. JavaScript
In theClass
It’s also a grammar candy.
For example, we declare a Class in Javascript:
Class Animal {
eat() {
console.log('eat food')}}Copy the code
The Animal class is equivalent to the following:
function Animal() {}
Object.defineProperty(Animal.prototype, 'eat', {
value: function() { console.log('eat food'); },
enumerable: false,
configurable: true,
writable: true
});
Copy the code
Class decorator
Class decorators are applied to class constructors and can be used to observe, modify, or replace class definitions.
function setDefaultDesc(constructor: Function){
constructor.prototype.desc = 'Class decorator Properties'
}
@setDefaultDesc
class Animal {
name: string;
desc: string;
constructor() {
this.name = 'dog'; }}let animal= new Animal();
console.log(animal.desc) // 'Class decorator Properties'
Copy the code
Here is an example of using overloaded functions.
functionclassDecorator<T extends {new(... args:any[]):{}}>(constructor:T) {return class extends constructor {
newProperty = "new property";
desc = "override";
}
}
@classDecorator
class Animal {
property = "property";
desc: string;
constructor(m: string) {
this.desc = m;
}
}
console.log(new Animal("world")); // Animal: {property: "property", desc: "override", newProperty: "new property" }
Copy the code
The meaning of this code is: be classDecorator decoration class if there is no newProperty or desc attribute, will increase the value of the corresponding attributes and corresponding, if there is this property will override the value of the attribute.
Method decorator
The method decorator declaration precedes the declaration of a method (right next to the method declaration). It is applied to the method’s property descriptor and can be used to monitor, modify, or replace the method definition.
The method decorator expression is called as a function at run time, passing in the following three arguments:
- targetThe prototype of the current object (if Employee is an object, i.e
Employee.prototype
) - PropertyKey Method name
- descriptorMethod property descriptor
Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
function logMethod(
target: Object,
propertyName: string,
propertyDesciptor: PropertyDescriptor): PropertyDescriptor {
// target === Employee.prototype
// propertyName === "greet"
// propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet")
const method = propertyDesciptor.value;
propertyDesciptor.value = function(... Args: any[]) {// Convert argument list to string const params = args.map(a => json.stringify (a)).join(); Const result = method.apply(this, args); // Call the method and return the result const result = method.apply(this, args); Const r = json.stringify (result); // Display function Call details in the console console.log(' Call:${propertyName}(${params}) = >${r}`); // Returns the result of the callreturn result;
}
return propertyDesciptor;
};
class Employee {
constructor(
private firstName: string,
private lastName: string
) {
}
@logMethod
greet(message: string): string {
return `${this.firstName} ${this.lastName} : ${message}`;
}
}
const emp = new Employee('March style'.'Flowers on a Stranger');
emp.greet('Flowers in March'); // return: 'Flowers on March: Flowers on March'
Copy the code
Accessor decorator
Accessors are just the getter and setter parts of a property in a class declaration. Accessor decorators are declared before accessors are declared. Accessor decorators are property descriptors applied to accessors that can be used to observe, modify, or replace the definition of accessors.
Accessor decorator expressions are called as functions at run time, passing in the following three arguments:
- targetThe prototype of the current object (if Employee is an object, i.e
Employee.prototype
) - PropertyKey Method name
- descriptorMethod property descriptor
Object.getOwnPropertyDescriptor(Employee.prototype, propertyKey)
The following is an example of using the accessor decorator (@signals), which works with a member of the Point class:
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { returnthis._y; }}Copy the code
Attribute decorator
The attribute decorator function takes two arguments:
- Target Indicates the prototype of the current object
- PropertyKey Specifies the name of the property
function logParameter(target: Object, propertyName: string) {// The value of the propertylet_val = target[propertyName]; // Property get method const getter = () => {console.log(' get:${propertyName}= >${_val}`);
return_val; }; / / propertiessetConst setter = newVal => {console.log(' Set:${propertyName}= >${newVal}`); _val = newVal; }; // Delete attributes.if(delete target[propertyName]) {// Use getter and setter to create a new property object.defineProperty (target, propertyName, {get: getter,set: setter,
enumerable: true,
configurable: true
});
}
}
class Employee {
@logParameter
name: string;
}
const emp = new Employee();
emp.name = 'Flowers on a Stranger'; // Set: name => console.log(emp.name); // Get: name =Copy the code
Parameter decorator
The argument decorator function takes three arguments:
- Target Indicates the prototype of the current object
- PropertyKey Method name
- The position of the index parameter in the parameter array
function logParameter(target: Object, propertyName: string, index: number) {// Generate metadata for the corresponding method // Keep the position of the modified Parameter const metadataKey = 'log_${propertyName}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');
Copy the code
In the above code example, the target instance emp is Employee, the propertyName value is greet, and the index value is 0.
Decoration factory
Let’s imagine a scenario where we need several decorators to print the names of some properties, the class itself, methods, and parameters in a class.
import { logClass } from './class-decorator';
import { logMethod } from './method-decorator';
import { logProperty } from './property-decorator';
import { logParameter } from './parameter-decorator'; // Assuming we already have the above decorators, we should do the following.function log(... args) { switch (args.length) {case3: // Can be a method decorator or a parameter decoratorif (typeof args[2] === "number") {// If the third argument is number then its index is its argument decoratorreturn logParameter.apply(this, args);
}
return logMethod.apply(this, args);
case2: // Attribute decoratorreturn logProperty.apply(this, args);
case1: // class decoratorreturn logClass.apply(this, args); Default: // throw new Error('Not a valid decorator'); }} @log
class Employee {
@log
private name: string;
constructor(name: string) {
this.name = name;
}
@log
greet(@log message: string): string {
return `${this.name} says: ${message}`; }}Copy the code
A decorator factory is a simple function that returns a decorator of one type.
Metadata Reflection API
@Reflect.metadata('name'.'A')
class A {
@Reflect.metadata('hello'.'world')
public hello(): string {
return 'hello world'
}
}
Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // 'world'
Copy the code
- Relfect Metadata, which can be used to add custom information to a class via decorators
- This information is then extracted by reflection
- You can also add this information through reflection
Relfect is used for reflection. It allows a running application to examine, or “self-examine,” itself and directly manipulate internal properties and methods. Reflection is a concept that has been widely used in Java/c# and many other languages
Here’s a quick example:
function logParameter(target: Object, propertyName: string, index: number) {// getMetadata from the target Object const indices = reflect.getmetadata (' log_${propertyName}_parameters`, target, propertyName) || []; indices.push(index); // Define the metadata as the target object reflect.definemetadata (' log_${propertyName}_parameters`, indices, target, propertyName); } // The property decorator uses the reflection API to get the runtime type of the propertyexport function logProperty(target: Object, propertyName: string): void {var t = reflect.getMetadata ("design:type", target, propertyName);
console.log(`${propertyName} type: ${t.name}`); // name type: String
}
class Employee {
@logProperty
private name: string;
constructor(name: string) {
this.name = name;
}
greet(@logParameter message: string): string {
return `${this.name} says: ${message}`; }}Copy the code
In the example above, we used the reflection metadata design key [Design: Type]. There are only three:
- Type metadata uses metadata keys
design:type
- Parameter type metadata uses metadata keys
design:paramtypes
- Return type metadata using metadata keys
design:returntype
conclusion
This article introduces class decorators, method decorators, accessor decorators, property decorators, parameter decorators, decorator factories, and metadata Reflection. It is also a summary of my learning process. Every week will continue to update different technologies, students who like can like and follow, we make progress together. If you want to learn some technology, please leave a message in the comments section. I will try my best to write what you are interested in.