TypeScript 中的装饰器
什么是装饰器
装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数。
装饰器必须使用 @expression
方式使用,expression
必须是一个函数。
开启装饰器
// tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
简单使用demo
function sealed(target: any) {
// do something with 'target' ...
}
@sealed
class SealedPackage {}
装饰器工厂
function color(value: string) {
// this is the decorator factory, it sets up
// the returned decorator function
return function (target: any) {
// this is the decorator
// do something with 'target' and 'value'...
}
}
多个装饰器
多个装饰可在同一行定义
@f @g x
或者多行定义
@f
@g
x
调用顺序
- The expressions for each decorator are evaluated top-to-bottom.
- The results are then called as functions from bottom-to-top.
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
//first(): factory evaluated
//second(): factory evaluated
//second(): called
//first(): called
Class装饰器
function sealed(constructor: Function) {
Object.seal(constructor)
Object.seal(constructor.prototype)
}
@sealed
class BugReport {
type = 'report'
title: string
constructor(t: string) {
this.title = t
}
}
防止装饰器修改构造函数
function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
reportingURL = "http://www...";
};
}
@reportableClassDecorator
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
}
const bug = new BugReport("Needs dark mode");
console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"
// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;
Property 'reportingURL' does not exist on type 'BugReport'.
Method 装饰器
方法修饰符是在方法声明之前声明的。
装饰器应用于方法的属性描述符,可用于观察、修改或替换方法定义。
装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:
- 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.
下面这个demo 是将方法设置为 enumerable = false
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value
}
}
class Greeter {
greeting: string
constructor(message: string) {
this.greeting = message
}
@enumerable(false)
greet() {
return 'Hello, ' + this.greeting
}
}
Accessor 装饰器
Accessor 装饰器是在访问器声明之前声明的。
Accessor 装饰器应用于访问器的属性描述符,可用于观察、修改或替换访问器的定义。
装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:
- 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.
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;
};
}
Property 装饰器
属性装饰器是在属性声明之前声明的。
装饰器的表达式将在运行时作为函数调用,并带有以下两个参数:
- 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.
这里的 @format(“Hello,%s”)是一个 装饰器工厂。
调用 @format(“Hello,%s”)时,它会使用 Reflect 添加 metadata entry。
调用 getFormat 时,它读取 metadata entry 的值。
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)
}
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)
}
}
Parameter 装饰器
参数装饰器是在参数声明之前声明的。
参数装饰器应用于类构造函数或方法声明的函数。
装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:
- 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.
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)
}
}
class BugReport {
type = 'report'
title: string
constructor(t: string) {
this.title = t
}
@validate
print(@required verbose: boolean) {
if (verbose) {
return `type: ${this.type}\ntitle: ${this.title}`
} else {
return this.title
}
}
}