本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習曆程,也為了友善回顧知識;故文章内容較為随意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~
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
,則 decorators 為
a3
,a1最先進入,a3最後進入;但當方法被調用時,裝飾器的執行順序是從右到左,即
[a1 ,a2 ,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);
- 類不支援提升,
必須在使用前聲明(寫在TestService 前面),否則OtherService
不起作用Reflect.getMetadata('design:paramtypes', target)
嘗試将OtherService放到TestService後面,觀察運作結果
- 要想在非裝飾器函數中取得中繼資料,需要對應的類(或屬性或方法)有一個裝飾器(此裝飾器可以不起其他作用)
嘗試注釋掉
,觀察運作結果@ClassDec(), @PropertyDec(), @MethodDec()