Github blog address
Decorators are still in the proposal stage. To use decorators in javascript, we must leverage the transcoding capabilities of Babel or typescript
Why decorators
The introduction of decorators makes it easier to decouple and reuse code logic. Let me give you an example
Take a very common requirement. Suppose we have a class Network that has an asynchronous getList method
class Network {
async getList() {
return awaitlist(); }}Copy the code
One day, we want to load it globally, so we might write this
class Network {
async getList() {
loading.show();
const res = await list();
loading.hide();
returnres; }}Copy the code
If you need to use global loading for another method, you may need to write it again. And this code also invades the logic of the function itself. Using a decorator is a relatively elegant way to solve this problem.
Implement a loadingDecorator decorator
function loadingDecorator(target, key, descriptor) {
descriptor.value = async function (. args) {
loading.show();
await descriptor.value.apply(this, args);
loading.hide();
};
}
Copy the code
Use our decorators
class Network {
@loadingDecorator
async getList() {
return awaitlist(); }}Copy the code
So, whenever a method needs to be loaded, use the @loadingDecorator decorator. In this way, both logical decoupling and good code reuse can be realized
Typescript transcoding looks like this for those of you who are interested
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect= = ="object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Copy the code
What is a decorator
Decorators are special declarations that can be applied to class declarations, methods, properties, accessors, or parameters. The use of decorators is @decorator. A decorator is a function that is called at run time to make some modifications to the class. Note that in javascript, decorators can only be used for classes, not ordinary functions. The reason is that there is a function promotion, and designers can refer to a discussion to reduce some of the complexity
The following defines a decorator function and applies it to the class
function sealed(target) {
// do something with 'target' ...
}
@sealed
class A {}
Copy the code
This is code similar to the following
A = sealed(A);
Copy the code
- Decoration factory
A decorator is essentially a function, so you can leverage the power of closures to do more. A decorator factory is a function that returns a function that will be called at run time
// For example, a factory decorator to add color
function addColor(color: string) {
console.log("run", color);
return function (target) {
if(! target.colorList) { target.prototype.colorList = []; } target.prototype.colorList.push(color); }; }@addColor("red")
class People {}
new People().colorList; // ['red']
Copy the code
- Multiple decorator combination decorator is also supported multiple use together, or the above color example, add multiple different color decorators
@addColor("blue")
@addColor("red")
@addColor("yellow")
class People {}
// log: run blue
// log: run red
// log: run yellow
new People().colorList; // ['yellow','red','blue']
Copy the code
From the information above, we can know.
- Decorator definitions are executed from top to bottom
- The decorator runtime decorates classes from bottom to top
Basic use of decorators
Class Decorators
Class decorators act on class constructors and can be used to modify or replace a class definition
A decorator function is signed as follows:
type decorator = (target: Function) = > Function | void;
Copy the code
It takes the decorated class as an argument to the target function, and if the decorator function has a return value, it uses that return value as the new class.
- There is no return value
For example, if you want to modify a class directly, add a static method to it
function addLog(target) {
target.log = function () {
console.log("hello world");
};
}
@addLog
class People {}
People.log(); // 'hello world'
Copy the code
- Returns a value
Of course, you can return the value directly.
// For example, you want to inherit the decorated class
function logName(target) {
return class extends target {
log() {
console.log(this.name); }}; }@logName
class People {
name = "hello world";
}
new People().log(); // hello world
Copy the code
Class member decorator
The ones listed below are decorated to members of a class, so they can all be grouped together
Property Decorators
Property decorators are used to decorate properties, and the function signature is as follows
type decorator = (
target: Target | Target.prototype,
propertyKey: string
) = > void;
Copy the code
The parameters to the property decorator are defined as follows:
1. First parameter. If static methods are decorated, the class Target itself; If you decorate a prototype method, the class’s prototype object target.prototype
2. The second parameter. The name of this property
The return value of the property decorator is ignored, so if you need to modify the property value. There are two cases
- Static properties that can be used directly
Object.getOwnPropertyDescriptor(target, propertyKey)
andObject.defineProperty(target,propertyKey,{})
To get and modifydescriptor
- If it is an instance attribute, it cannot be changed directly and easily because the class has not yet been instantiated. What is the definition of instance properties, i.e. properties defined by the babel-plugin-proposal-class-properties direct syntax
class Target {
a = 1;
}
Copy the code
Such decorators are not useless, however, as they can easily collect metatype information in typescript, as discussed in a later article
Method Decorators
Method decorators are used to decorate methods and can be used to modify method definitions. The function signature of the method decorator is as follows
type decorator = (
target: Target | Target.prototype,
propertyKey: string,
descriptor: PropertyDescriptor
) = > Function | void;
Copy the code
Method decorator parameters are defined as follows:
1. First parameter. If static methods are decorated, the class Target itself; If you decorate a prototype method, the class’s prototype object target.prototype
2. The second parameter. The name of this method
3. The third parameter, the property descriptor of the method, can be retrieved directly from the method descriptor via Description. value
If the property decorator has a return value, the return value is used as the property descriptor for the method. Attributes of the object descriptor is called Reflect. GetOwnPropertyDescriptor (target, propertyKey) return values, visible in detail
const obj = { a: 1 };
Reflect.getOwnPropertyDescriptor(obj, "a");
/**
{value: 1, writable: true, enumerable: true, configurable: true}
**/
Copy the code
function log(target, key, descriptor) {
console.log(target, key, descriptor);
}
Copy the code
- Static/prototype method decorators add logs to methods
// Static or dynamic methods to add logs
function log(target, key, descriptor) {
const origin = descriptor.value;
descriptor.value = function (. args) {
console.log(Static log:, key);
origin.apply(this, args);
};
}
Copy the code
Accessor Decorators
Parameter Decorators
The parameter decorator’s function signature is as follows
type decorator = (
target: Target | Target.prototype,
propertyKey: string,
parameterIndex: number
) = > void;
Copy the code
Parameter decorator parameters are defined as follows:
1. First parameter. If you decorate the parameters of a static method, the class Target itself. If you’re decorating a prototype method parameter, the class’s prototype object target.prototype
2. The second parameter. The name of the function in which the argument is located
3, the third argument, the function argument list position subscript (number)
Execution order of various decorators
As follows:
Execute the instance member decorator (non-static) first, and then execute the static member decorator
2. When executing member decorators, execute parameter decorators first and then decorators applied to members
3. Execute the constructor parameter decorator after executing 1 and 2. Finally, the decorator applied to the class is executed
Typescript’s more powerful decorators
invue-property-decorator
The application of
Some of the uses mentioned above are more about using decorators in javascript scenarios to optimize the structure of our code. In typescript, decorators have an even more powerful capability to retrieve typescript definition type information at runtime.
Those of you who have written vue in typescript will use the vue-decorator-property library. In Prop we can see documentation like this
If you’d like to set type property of each prop value from its type definition, you can use
reflect-metadata
. SetemitDecoratorMetadata
to true. Import reflect-metadata before importing vue-property-decorator (importing reflect-metadata is needed just once.)
import "reflect-metadata";
import { Vue, Component, Prop } from "vue-property-decorator";
@Component
export default class MyComponent extends Vue {
@Prop() age! :number;
}
Copy the code
We don’t need to define vue in the options type of Prop. This capability is provided by typescript’s emitDecoratorMetadata feature. We see the above code after ts compilation effect is as follows, address
import { __decorate, __metadata } from "tslib";
import "reflect-metadata";
import { Vue, Component, Prop } from "vue-property-decorator";
let MyComponent = class MyComponent extends Vue {};
__decorate(
[Prop(), __metadata("design:type".Number)],
MyComponent.prototype,
"age".void 0
);
MyComponent = __decorate([Component], MyComponent);
export default MyComponent;
Copy the code
We can see that our type information is collected in metadata design:type, and we can retrieve this type information at runtime through some methods provided by reflect-Metadata.
Store the type of each decorated class/property/method in a global location with key design:type. We can use class/method/key to retrieve this type of information and do whatever we want.
Application in Node
Examples from a deeper understanding of typescript
If we want to write an HTTP interface based on a class declaration, instead of writing a lot of router.get/router.post. For example:
@Controller("/test")
class SomeClass {
@Get("/a")
someGetMethod() {
return "hello world";
}
@Post("/b")
somePostMethod(){}}Copy the code
Obviously, here we define two interfaces, /test/a and test/b. The key here is to implement the Controller and Post/Get decorators
Controller acts on the class and we define a meta key and use reflect.definemetadata to store the corresponding meta information
const PATH_METADATA = Symbol('path');const Controller = (path: string) :ClassDecorator= > {
return target= > {
Reflect.defineMetadata(PATH_METADATA, path, target); }}Copy the code
Implement another factory decorator that returns Get/Post
const PATH_METADATA = Symbol('path');const METHOD_METADATA = Symbol('method');const createMappingDecorator = (method: string) = > (path: string) :MethodDecorator= > {
return (target, key, descriptor) = > {
Reflect.defineMetadata(PATH_METADATA, path,target, key;
Reflect.defineMetadata(METHOD_METADATA, method, target, key); }}const Get = createMappingDecorator('GET');
const Post = createMappingDecorator('POST');
Copy the code
The createMappingDecorator takes an argument indicating whether this is a Get or Post and returns a decorator. The decorator call defineMetadata stores two keys, PATH_METADATA and METHOD_METADATA, with value being the request path and method, respectively.
Therefore, the above decoration can be compared to a storage structure of the following form
{
[PATH_METADATA]: {
UNDEIFINED: '/test'
GET: {someGetMethod: '/test'
},
POST: {somePostMethod: '/test'
}
},
[METHOD_METADATA]: {
GET: {someGetMethod: '/a'
},
POST: {somePostMethod:'/b'}}}Copy the code
Evaluates and maps the function to generate a route
/ / value
function mapRoute(instance: Object) {
const prototype = Object.getPrototypeOf(instance);
// Filter out the class methodName
const methodsNames = Object.getOwnPropertyNames(prototype)
.filter(item= >! IsConstructor (item) && isFunction (prototype (item)));return methodsNames.map(methodName= > {
const fn = prototype[methodName];
// Retrieve defined metadata via metadataKey, target, propertyKey
const route = Reflect.getMetadata(PATH_METADATA, instance, methodName);// /a or /b
const method = Reflect.getMetadata(METHOD_METADATA, instance, methodName);// GET or POST
return {
route,
method,
fn,
methodName,
pre
}
})
};
Reflect.getMetadata(PATH_METADATA, SomeClass); // '/test'
mapRoute(new SomeClass());
/** * [{ * route: '/a', * method: 'GET', * fn: someGetMethod() { ... }, * methodName: 'someGetMethod' * },{ * route: '/b', * method: 'POST', * fn: somePostMethod() { ... }, * methodName: 'somePostMethod' * }] * */
Copy the code
Finally, you simply tie the route information to the corresponding HTTP framework
Reflect-metadata more apis are available
typedi
Finally, a brief introduction to TypeDI
Introduction to reference documents.
Typedi is a typescript(javascript) dependency injection tool that builds easy-to-test and well-architected applications in Node.js and browsers. It has the following features:
- Property/constructor-based dependency injection
- Singleton/temporary service
- Can support multiple
container
Official website example, very convenient implementation of dependency injection use
import { Container, Service } from 'typedi';
@Service(a)class ExampleInjectedService {
printMessage() {
console.log('I am alive! '); }}@Service(a)class ExampleService {
constructor(
// because we annotated ExampleInjectedService with the @Service()
// decorator TypeDI will automatically inject an instance of
// ExampleInjectedService here when the ExampleService class is requested
// from TypeDI.
private injectedService: ExampleInjectedService
){}}const serviceInstance = Container.get(ExampleService);
// we request an instance of ExampleService from TypeDI
serviceInstance.injectedService.printMessage();
// logs "I am alive!" to the console
Copy the code
The last
Code word is not easy, one key three people will have good luck next year oh, I wish you all a happy New Year!!
The resources
typescript Decorators
Understand typescript in depth