装饰器目前正处于 stage2,如果要使用它,需要将 experimentalDecorators
置为 true。
{
"compilerOptions": {
"experimentalDecorators": true
}
}
装饰器可以添加到类的声明、方法、属性、访问器、参数。使用 @expression
进行申明,这个 expression
会在运行时被调用。例如 @sealed
:
function sealed(target) {
// do something with 'target' ...
}
// 使用
@sealed
Decorator Factories
一个工厂函数,用来返回一个装饰器
function color(value: string) {
// this is the decorator factory
return function (target) {
// this is the decorator
// do something with 'target' and 'value'...
};
}
// 使用
@color('flwkefjlew')
Decorator Composition
将装饰器组合在一起使用
@f @g x // 在同一行
@f
@g
x // 在多行
- 每一个装饰器的表达式被计算的顺序为从上到下(当它是一个工厂函数时)
- 结果计算从下到上
function f() {
console.log("f(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("f(): called");
};
}
function g() {
console.log("g(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("g(): called");
};
}
class C {
@f()
@g()
method() {}
}
//
function f() {
console.log("f(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("f(): called");
};
}
function g() {
console.log("g(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("g(): called");
};
}
class C {
@f()
@g()
method() {}
}
[LOG]: "f(): evaluated"
[LOG]: "g(): evaluated"
[LOG]: "g(): called"
[LOG]: "f(): called"
Decorator Evaluation
Class Decorators
The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition。
The expression for the class decorator will be called as a function at runtime, 并将类的 constructor 作为它唯一的参数。
If the class decorator returns a value, it will replace the class declaration with the provided constructor function.
注意:如果 you choose to return a new constructor function,you must take care to maintain the original prototype. The logic that applies decorators at runtime will not do this for you.
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
例子:实现一个装饰器,用来生成一个新的类,新的类继承自被装饰的类
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
function classDecorator<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
newProperty = "new property";
hello = "override";
constructor(...args: any[]) {
super()
}
};
}
const greeter = new Greeter('hello')
/* greeter
{
"property": "property",
"hello": "override",
"newProperty": "new property"
}
/*
Property Decorators
The expression for the property decorator will be called as a function at runtime, with the following two arguments:
- Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
- The name of the member.
NOTE A Property Descriptor is not provided as an argument to a property decorator 因为装饰器在类定义的时候就会运行,而 property 可能在类实例化时才会有值,所以属性装饰器拿不到属性的值即不会传属性描述符作为第三个参数.The return value is ignored too. As such, a property decorator can only be used to observe that a property of a specific name has been declared for a class.
We can use this information to record metadata about the property
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
Method Decorators
The expression for the method decorator will be called as a function at runtime, with the following three arguments:
- Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
- The name of the member.
- The Property Descriptor for the member.
NOTE The Property Descriptor will be undefined if your script target is less than ES5.
If the method decorator returns a value, it will be used as the Property Descriptor for the method.
NOTE The return value is ignored if your script target is less than ES5.
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
function enumerable(value: boolean) {
return function (
target: any, // Greeter.prototype
propertyKey: string, // greet
descriptor: PropertyDescriptor // { "writable": true,"enumerable": false, "configurable": true }
) {
descriptor.enumerable = value;
};
}
Accessor Decorators
NOTE TypeScript disallows decorating both the get and set accessor for a single member
The expression for the accessor decorator will be called as a function at runtime, with the following three arguments:
- Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
- The name of the member.
- The Property Descriptor for the member.
NOTE The Property Descriptor will be undefined if your script target is less than ES5.
If the accessor decorator returns a value, it will be used as the Property Descriptor for the member.
NOTE The return value is ignored if your script target is less than ES5.
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() {
return this._y;
}
}
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
Parameter Decorators
The parameter decorator is applied to the function for a class constructor or method declaration.
The expression for the parameter decorator will be called as a function at runtime, with the following three arguments:
- Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
- The name of the member.
- The ordinal index of the parameter in the function’s parameter list.
NOTE A parameter decorator can only be used to observe that a parameter has been declared on a method. The return value of the parameter decorator is ignored.
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(
requiredMetadataKey,
existingRequiredParameters,
target,
propertyKey
);
}
function validate(
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<Function>
) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(
requiredMetadataKey,
target,
propertyName
);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (
parameterIndex >= arguments.length ||
arguments[parameterIndex] === undefined
) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
Metadata
This library is not yet part of the ECMAScript (JavaScript) standard. However, once decorators are officially adopted as part of the ECMAScript standard these extensions will be proposed for adoption.
需要开启如下选项。
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}