本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~
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()
,觀察運行結果