ts可展开注释_TS语法之装饰器(注解)
寫在前面的話
本文只講解 TypeScript 中的裝飾器語法(下稱注解), 只會告訴你如何編寫一個自定義注解,且通過注解簡單的修改邏輯,不涉及 反射 或 元編程 等其他更進一步的代碼講解,如果有興趣可以自行搜索相關的進階寫法,比如這位:
開始
注1:裝飾器僅用于 class 語法中,所以以下舉例中只會使用 class 寫法。(其實是我只測試了 class 語法中的使用,而且也沒必要在普通方法上使用注解)
注2:tsconfig.json 中 target 必須為 ES5 或以上版本。且 experimentalDecorators 與 emitDecoratorMetadata 字段需為 true
由于我們寫的是 TypeScript,我們首先定義幾個 interface 和 type ,讓 IDE 有一些基本提示:
type Prototype = {
constructor: Function
} & any
?
type Constructor = { new(...args: any[]): {} };
?
interface FunctionAnnotation {
(target: Prototype, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): void;
}
?
interface ConstructorAnnotation {
(constructor: T): T;
}
?
interface PropertyAnnotation {
(target: Prototype, propertyKey: PropertyKey): void;
}
?
interface ParameterAnnotation {
(target: Prototype, propertyKey: PropertyKey, parameterIndex: number): void;
}
?
其中,PropertyKey 和 TypedPropertyDescriptor 都在 lib.es5.d.ts 文件中有定義,其值為:
declare type PropertyKey = string | number | symbol;
interface TypedPropertyDescriptor {
enumerable?: boolean;
configurable?: boolean;
writable?: boolean;
value?: T;
get?: () => T;
set?: (value: T) => void;
}
如果有人不喜歡泛型寫法,也可以將 TypedPropertyDescriptor 替換成 PropertyDescriptor
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}這里使用泛型僅僅是因為我不喜歡 any,Prototype 類型中加上 any 是因為沒有辦法只能用 any,原因是類型定義中沒有辦法用 symbol 類型的值定義成 key,否則就可以寫成 type Prototype = {[key: string]: Function; [key: number]: Function; [key: symbol]: Function;} 了
如果有人不知道 PropertyDescriptor 是干嘛的,請參考 Object.defineProperty()
下面我們直接用例子教你如何自定義一個注解。
方法注解
寫法:
export function logFuncCall(): FunctionAnnotation {
return function (target, propertyKey, descriptor) {
if (typeof descriptor.value === "function") {
let value: Function = descriptor.value;
// @ts-ignore
descriptor.value = function (...args: any) {
console.log(`${Date()} ${target.constructor.name}["${String(propertyKey)}"] be Called`);
return value.call(this, ...args);
};
}
};
}
注:若注解中需要傳入參數,參考正常方法的寫法添加需要的參數即可,不贅述。(注解與正常方法的區別僅僅在于方法調用前面多了一個@符號而已)
參數詳解:target:被注解類的 prototype ,本例中為 Test.prototype,如果被注解的方法是靜態方法,則為類本身,也就是 Test
propertyKey:被注解的方法名,本例中為 method
descriptor:屬性描述,參考 Object.defineProperty(o, p, attributes)中 attributes 參數
需要注意的是,如果注解中不需要傳入任何參數,則可以省略注解中最外層的一層方法,變成如下:
export function logFuncCall(target, propertyKey, descriptor) {
if (typeof descriptor.value === "function") {
let value: Function = descriptor.value;
// @ts-ignore
descriptor.value = function (...args: any) {
console.log(`${target.constructor.name}["${String(propertyKey)}"] be Called`);
return value.call(this, ...args);
};
}
}
這時,注解則可以寫成 @logFuncCall 的形式(去掉了括號),否則括號不能省略。下同,不再贅述。本文并未采用省略寫法。
直接編譯調試運行如下代碼:
class Test {
@logFuncCall()
method() {}
}
let t = new Test("ttt");
t.method();
這時,控制臺中會輸出:
Test["method"] be Called
Getter/Setter注解
同 方法注解;
與方法注解的區別在于,Getter/Setter 注解的 descriptor 中只有對應的 get 和 set 字段,而 value 字段為 undefined,而方法注解只中有 value 字段,而 get 和 set 字段為 undefined
所以通用的方法注解應該如下:
export function logFuncCall(): FunctionAnnotation {
return function (target, propertyKey, descriptor) {
if (typeof descriptor.value === "function") {
let value: Function = descriptor.value;
// @ts-ignore
descriptor.value = function (...args: any) {
console.log(`${target.constructor.name}["${String(propertyKey)}"] be Called`);
return value.call(this, ...args);
};
}
if (typeof descriptor.get === "function") {
let get = descriptor.get;
descriptor.get = function () {
console.log(`${target.constructor.name}["get ${String(propertyKey)}"] be Called`);
return get.call(this);
};
}
if (typeof descriptor.set === "function") {
let set = descriptor.set;
descriptor.set = function (value) {
console.log(`${target.constructor.name}["set ${String(propertyKey)}"] be Called`);
set.call(this, value);
};
}
};
}
類注解
寫法:
export function logCreate(): ConstructorAnnotation {
return (constructor: T) => {
return class extends constructor {
constructor(...args: any[]) {
console.log(`${Date()} ${constructor.name} new an instance`);
super(...args);
}
};
};
}
參數詳解constructor:被注解類的原對象,而不是類的 prototype
上面這種寫法適用于大部分情況,類似于繼承了被注解的類
如果你不希望任何人重寫此類的方法,可以用如下的匿名寫法:(在類的 constructor 方法中返回一個對象,則 new 對象時 this 指向該對象,而非類的實例)
export function logCreate(): ConstructorAnnotation {
return (constructor: T) => {
return class {
constructor(...args: any[]) {
console.log(`${Date()} ${constructor.name} new an instance`);
return new constructor(...args);
}
};
};
}
這種寫法需要注意的是:環境中被注解的類將會變為一個注解返回的匿名內部類,其不繼承于原先的類,在類外也無法調用原先類的所有方法,只有使用 new 新建對象后,才會返回一個原先類的對象(除非你在內部類中自己定義了靜態方法去調用原先類的靜態方法)
被這種寫法注解的類如果被繼承,則只能在構造函數中對自身做出一定修改,且所有靜態方法不可通過類名調用
屬性注解
寫法:
export function logGetSet(): PropertyAnnotation {
return function (target, propertyKey) {
let s = "_" + String(propertyKey);
Object.defineProperty(target, propertyKey, {
get() {
console.log(`get ${String(propertyKey)}`);
return this[s];
},
set(v) {
console.log(`set ${String(propertyKey)} => ${v}`);
this[s] = v;
},
configurable: true,
enumerable: true,
});
};
}
參數詳解target:參考方法注解
propertyKey:參考方法注解
注:propertyKey 是有可能為 symbol 或 number 類型的,所以 String() 大概率不能省略
這里需要注意的是:由于注解執行緊跟在類的定義之后,所以如果沒有在定義時賦默認值,則 target 中是不會有對應的屬性字段的,這時可以選擇自己定義 Getter/Setter 然后在其中編寫對應的方法,也可以選擇在項目中引入 import 'reflect-metadata',使用 反射-元數據 編程(參考本文開頭給出的鏈接 )
如果注解的是靜態字段,且在定義時就賦過值,則 target 中將會有對應的屬性字段且有值
參數注解
寫法:
export function logParam(): ParameterAnnotation {
return function (target, propertyKey, parameterIndex) {
...
};
}
參數詳解target:參考方法注解
propertyKey:參考方法注解
parameterIndex:被注解參數位置,從 0 開始
這里除了反射以外好像沒別的用法?或者跟方法注解聯動,畢竟方法也是對象,是可以保存參數的,案例:
export function logFuncCall(): FunctionAnnotation {
return function (target, propertyKey, descriptor) {
if (typeof descriptor.value === "function") {
let value: Function = descriptor.value;
// @ts-ignore
descriptor.value = function (...args: any) {
// @ts-ignore
value.logParam?.forEach(i => console.log(`第${i}參數: ${args[i]}`));
return value.call(this, ...args);
};
}
}
export function logParam(): ParameterAnnotation {
return function (target, propertyKey, parameterIndex) {
target[propertyKey].logParam = [...target[propertyKey].logParam ?? [], parameterIndex];
};
}
class Test {
@logFuncCall()
set(@logParam() log: string) {}
}
new Test.set("aaa")
> 第0參數: aaa
這樣同時注解 @logFuncCall() 和 @logParam 之后,就可以打印傳入的參數了
結尾
另外附上一些自己寫的小案例:
type Prototype = {
constructor: Function
__proto__: Prototype
} & any
?
/** (運行時)當父類中沒有對應方法時提示報錯 */
export function override(): FunctionAnnotation {
return (target, propertyKey) => {
if (target.__proto__?.[propertyKey] === undefined) {
console.error(`Method "${String(propertyKey)}" Not A Override Function`);
}
};
}
?
/** 自動調用父類的對應方法 */
export function autoCallSuper(): FunctionAnnotation {
return (target, propertyKey, descriptor) => {
if (typeof descriptor.value === "function") {
let value = descriptor.value;
if (typeof target.__proto__?.[propertyKey] === "function") {
// @ts-ignore
descriptor.value = function (...args: any) {
target.__proto__[propertyKey].apply(this, args);
return value.apply(this, args);
};
} else {
console.error(`${target.__proto__.constructor.name} No Function Name is "${String(propertyKey)}"`);
}
} else {
console.warn(`"autoCallSuper" Annotation Only Used On NormalFunction, Not Getter/Setter`);
}
};
}
測試案例:
@logCreate()
class A {
@logFuncCall()
a() {
console.log("A");
}
}
?
@logCreate()
class B extends A {
constructor() {
super();
this.a();
}
@logFuncCall()
@autoCallSuper()
a() {
console.log("B");
}
static b() {}
}
?
console.dir(B);
console.dir(new B());
B.b();
調試 log:
[class (anonymous) extends B] // console.dir(B);
B new an instance // new B()
A new an instance // super被調用
B["a"] be Called // 調用a方法
A["a"] be Called // 由于autoCallSuper,先調用super方法
A // super方法打印A
B // 重寫方法打印B
B {} // console.dir(new B());打印new出來的對象
Function["b"] be Called // B.b();
如果類注釋采用了匿名寫法,則 log 為:
[class (anonymous)] // 匿名類沒有繼承B
B new an instance // new B()
A new an instance // super被調用,但是返回了A對象,導致this指針變為了A對象的實例
A["a"] be Called // this.a()
A // 方法打印A
A {} // console.dir(new B());打印new出來的對象
Uncaught TypeError: B.b is not a function // 匿名類沒有繼承B,自然也就沒有B里的靜態方法,報錯
最后,小心溢出,不用
最后,性能太低,不用
最后,還沒成真的語法呢,不用
最后,編寫一時爽,debug火葬場
總結
以上是生活随笔為你收集整理的ts可展开注释_TS语法之装饰器(注解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python数据分析并生成报告_pand
- 下一篇: vscode智能提示css的插件_Vis