js 中的装饰器还是一个提案,需要 babel 才可以使用。它还是一项实验性特性,在未来的版本中可能会发生改变。
装饰器是一个函数,它只能作用于类不能作用于(因为函数提升),它以一个@
符号开头,如下:
function d(target) { return target }
@d
class A {}
// 它相当于把类作为参数传递给 d 函数,然后再返回一个被函数修改过的类
// 等同于
A = d(A) || A
复制代码
它还可以传递自定义参数和使用多个装饰器
function a(p) {
console.log(1)
return (target => {
console.log(2)
return target
})
}
function b(target) {
console.log(3)
return target
}
@b
@a(1)
class A {}
// 打印顺序是 1 2 3
复制代码
多个装饰器除了一行写一个,也可以全都写在一行@b @a(1)
。
在 react-redux 应用中使用装饰器可以这样使用 connect 方法
@connect(mapStateToProps, mapDispatchToProps)
export default class Comp extends Component {}
复制代码
方法的装饰
装饰器除了作用于类,还可以作用于类的方法。
function d(target, name, desc) {
// target 是类的原型对象
// name 是函数的名字
// desc 是属性描述符
let oldValue = desc.value;
desc.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return desc
// 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
}
class A {
@d
fn () {}
}
复制代码
在 TypeScript 中方法装饰器的 target 参数,对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
属性装饰器
在 TypeScript 中装饰器还可以作用于属性。
function d(target, name) {
// target 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
// name 属性的名字
}
class A() {
@d
greeting: string;
}
复制代码
参数装饰器
TypeScript 中还可以装饰参数,
function Query(target, name, index) {
// target 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
// name 方法的函数名
// index 参数的索引 从 0 开始
// 参数装饰器的返回值会被忽略。
}
class A() {
fn(@Query query: Object) {
}
}
复制代码
存取装饰器
TypeScript 中装饰器也可以装饰存取声明函数。
function d(target, name, desc) {
// target 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
// name 函数名
// desc 属性描述符
return desc
// 如果方法装饰器返回一个值,它会被用作属性描述符
}
class A() {
@d
get a() {}
}
复制代码
装饰器求值
TypeScript 多个装饰器执行顺序是,
- 参数装饰器,然后是方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员
- 参数装饰器,然后是方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员
- 参数装饰器应用到构造函数
- 类装饰器应用到类
是从里到外的执行顺序。
元数据
reflect-metadata 库是来支持实验性的metadata API,这个库还不是ECMAScript (JavaScript)标准的一部分。
要使用元数据需要先开启experimentalDecorators emitDecoratorMetadata
选项。
当启用后,只要reflect-metadata库被引入了,设计阶段添加的类型信息可以在运行时使用。
function D(...rest) {
}
class A {
@D
readonly a: string;
}
class B {
fn(@D x: A) {
console.log(x)
}
}
复制代码
会被转义成
function D(...rest) {
}
class A {
}
__decorate([
D,
__metadata("design:type", String)
], A.prototype, "a", void 0);
class B {
fn(x) {
console.log(x);
}
}
__decorate([
__param(0, D),
__metadata("design:type", Function),
__metadata("design:paramtypes", [A]),
__metadata("design:returntype", void 0)
], B.prototype, "fn", null);
复制代码
它加入了__metadata
函数,将装饰器的元信息存取起来,这样就可以随时获取出来。
nodejs 的 nestjs 框架就大量使用到了这些元信息。