本系列文章是本人学习相关知识时所积累的笔记,以记录自己的学习历程,也为了方便回顾知识;故文章内容较为随意简练,抱着学习目的来的同学务必转移他处,以免我误人子弟~
Decorators & metadata reflection in TypeScript: From Novice to Expert (Part I)
从 JavaScript 到 TypeScript 4 - 装饰器和反射
关于装饰器函数需不需要返回值的问题
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
即
- 类装饰器接收1个参数(当前类的构造函数F),通过修改该构造函数并返回可达到修改类的效果(也可不返回)
- 属性装饰器接收2个参数(当前类的原型对象和属性名),无需返回值
静态属性接收当前 类的构造函数 和 属性名
- 方法装饰器接收3个参数(当前类的原型对象、方法名和方法描述对象),通过修改方法描述对象并返回达到修改方法的效果(也可不返回)
静态方法接收当前 类的构造函数 、属性名 和 方法描述对象
- 参数装饰器接收3个参数(当前类的原型对象、方法名和参数所在方法的索引),无需返回值
查看 MethodDecorator 的ts解析代码
ts代码:
class C {
@log
foo(n: number) {
return n * 2;
}
}
function log(target: Object, key: string, descriptor: any) {
...
}
解析后的js代码:
var C = (function () {
function C() {
}
C.prototype.foo = function (n) {
return n * 2;
};
Object.defineProperty(C.prototype, "foo",
__decorate(
[log],
C.prototype,
"foo",
Object.getOwnPropertyDescriptor(C.prototype, "foo")
));
return C;
})();
var __decorate = this.__decorate || function (decorators, target, key, desc) {
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
return Reflect.decorate(decorators, target, key, desc);
}
switch (arguments.length) {
case 2:
return decorators.reduceRight(function(o, d) {
return (d && d(o)) || o;
}, target);
case 3:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key)), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key, o)) || o;
}, desc);
}
};
为什么是 reduceRight ?
因为typescript的装饰器是 先进后出 的原则;假设有3个方法装饰器a1
a2
a3
,则 decorators 为[a1 ,a2 ,a3]
,a1最先进入,a3最后进入;但当方法被调用时,装饰器的执行顺序是从右到左,即a3 a2 a1
的顺序
如果是 MethodDecorator
,即 case 4,则 __decorate
最终会返回一个方法描述对象,如果某个方法装饰器返回方法描述对象desc,则 __decorate
返回desc,否则放回方法原生的方法描述对象;先执行的装饰器所返回的方法描述对象会被后面返回的取代
Tips
在 Typescript
中,Reflect.getMetadata
的使用有很多规矩需要注意:
import "reflect-metadata"
type Constructor<T = any> = new (...args: any[]) => T;
const ClassDec = (): ClassDecorator => {
return (target) => {
return;
}
};
const PropertyDec = (): PropertyDecorator => {
return (target, key) => {
return;
}
};
const MethodDec = (): MethodDecorator => {
return (target, key, desc) => {
return;
}
};
class OtherService {
constructor(){}
a=0;
}
@ClassDec()
class TestService {
constructor(public readonly otherService: OtherService) { };
@PropertyDec()
public name: string;
@MethodDec()
public handle(event: number) {
console.log(this.otherService.a);
}
}
const Factory = <T>(target: Constructor<T>): T => {
// 获取所有注入的服务
console.log(Reflect.getMetadata('design:paramtypes', target));
console.log(Reflect.getMetadata('design:type', target))
console.log(Reflect.getMetadata('design:returntype', target));
console.log(Reflect.getMetadata('design:paramtypes', target.prototype, "handle"));
console.log(Reflect.getMetadata('design:type', target.prototype, "handle"))
console.log(Reflect.getMetadata('design:returntype', target.prototype, "handle"));
console.log(Reflect.getMetadata('design:paramtypes', target.prototype, "name"))
console.log(Reflect.getMetadata('design:returntype', target.prototype, "name"));
console.log(Reflect.getMetadata('design:type', target.prototype, "name"))
return new target();
};
Factory(TestService);
-
类不支持提升,
OtherService
必须在使用前声明(写在TestService 前面),否则Reflect.getMetadata('design:paramtypes', target)
不起作用尝试将OtherService放到TestService后面,观察运行结果
-
要想在非装饰器函数中取得元数据,需要对应的类(或属性或方法)有一个装饰器(此装饰器可以不起其他作用)
尝试注释掉
@ClassDec(), @PropertyDec(), @MethodDec()
,观察运行结果